mirror of
https://github.com/hanzoai/datastore.git
synced 2026-09-26 13:55:07 +00:00
pkg.hanzo.ai serves what we build, and says so when we have not
The worker fetched from upstream's package host and renamed the file in flight, so `datastore-server` from pkg.hanzo.ai resolved to another project's binary, served under our host, stamped with our x-served-by. That is worse than serving nothing — it hands someone else's build to a user who believes it is ours, and it makes our package channel depend on a third party's release schedule and uptime. The upstream base and the name map are gone. It serves our own artifact store when PKG_BASE names one, and answers 503 with the image we DO publish when it does not. hanzo.yml already records why there is no apt/tgz lane yet; a 503 that names ghcr.io/hanzoai/datastore beats a 200 carrying the wrong bytes. docs/_site is deleted — 50 files, 2.2M, of upstream's built Mintlify site: their footer with four columns of links to their pricing, careers and legal pages, their AI search configured as organizationDisplayName "ClickHouse", their trademark page, and their "© ClickHouse, Inc. HQ" line. Nothing in this repo builds or serves it, so it was vendored marketing for a company we are not. LICENSE and NOTICE at the root are untouched. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
# Customizations
|
||||
|
||||
Custom scripts that customize the look and behavior of the Mintlify site.
|
||||
These are loaded via the `scripts` array in `docs.json` and run on every page.
|
||||
|
||||
See Mintlify's docs on [custom scripts](https://www.mintlify.com/docs/customize/custom-scripts) for more information.
|
||||
|
||||
## Scripts
|
||||
|
||||
- `ask-ai-button.js` — tweaks the Ask-AI button styling/behavior.
|
||||
- `custom-footer.js` — renders the custom site footer (`#ch-custom-footer`).
|
||||
- `galaxy.js` — initializes ClickHouse Galaxy analytics, tracks docs and knowledge-base page lifecycle events across Mintlify SPA navigation, sends canonical `https://clickhouse.com/docs` traffic to the production Galaxy API and `.mintlify.app` preview traffic to the development Galaxy API (disabled on localhost and unknown origins), and adds attribution plus the Galaxy visitor ID to ClickHouse Cloud links only on canonical production docs.
|
||||
- `inkeep-init.js` — replaces Mintlify's native search (⌘K / navbar search bar) with the Inkeep search modal. Search-only; AI chat stays on Kapa.
|
||||
- `kapa-init.js` — bootstraps the Kapa.ai RAG widget.
|
||||
- `navbar-cta.js` — adds the "Get started" CTA to the navbar.
|
||||
- `nav-icon-preload.js` — warms the small desktop navbar dropdown SVGs at low priority as soon as the document is ready so icons are available before their menus are first opened.
|
||||
- `raise-issue-link.js` — repoints Mintlify's default "Raise issue" feedback link at the repository's Documentation issue form (`.github/ISSUE_TEMPLATE/60_documentation-issue.yaml`), pre-filling the current page path.
|
||||
- `scroll-reset.js` — restores scroll-to-top on forward client-side navigation, which Next.js skips when an announcement banner is configured. Leaves back/forward (popstate) alone so the browser/router can restore the prior position, and scrolls to the anchor on cross-page hash links.
|
||||
- `tab-nav.js` — wires the desktop top-nav tabs (Home / Database / Solutions / Integrations) to their target URLs, keeping navigation within the active locale and base path.
|
||||
- `clickhouse-sql-lexer-wasm.js` — exposes ClickHouse's SQL lexer (`src/Parsers/Lexer.cpp` compiled to WebAssembly) as a base64 global. Snapshot from ClickHouse `programs/server/play.html`; re-extract if the upstream lexer/keyword list changes.
|
||||
- `clickhouse-sql-highlight.js` — re-highlights `language="sql"` code blocks using the WASM lexer (ClickHouse-native colors, matching play.html / clickhouse-client) in place of Shiki. Must load after `clickhouse-sql-lexer-wasm.js`. Other languages are left as Shiki rendered them.
|
||||
|
||||
To wire a new script up, add a `{ "src": "/_site/customizations/<name>.js" }`
|
||||
entry to the top-level `scripts` array in `docs.json`.
|
||||
@@ -1,108 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var BTN_ID = 'ch-ask-ai-btn';
|
||||
var MOBILE_BTN_ID = 'ch-ask-ai-btn-mobile';
|
||||
|
||||
var sparkleSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"'
|
||||
+ ' class="ch-ai-icon size-4 shrink-0 text-gray-700">'
|
||||
+ '<g fill="currentColor">'
|
||||
+ '<path d="M5.658,2.99l-1.263-.421-.421-1.263c-.137-.408-.812-.408-.949,0l-.421,1.263-1.263,.421c-.204,.068-.342,.259-.342,.474s.138,.406,.342,.474l1.263,.421,.421,1.263c.068,.204,.26,.342,.475,.342s.406-.138,.475-.342l.421-1.263,1.263-.421c.204-.068,.342-.259,.342-.474s-.138-.406-.342-.474Z" fill="currentColor" data-stroke="none" stroke="none"></path>'
|
||||
+ '<polygon points="9.5 2.75 11.412 7.587 16.25 9.5 11.412 11.413 9.5 16.25 7.587 11.413 2.75 9.5 7.587 7.587 9.5 2.75" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></polygon>'
|
||||
+ '</g></svg>';
|
||||
|
||||
function injectStyles() {
|
||||
if (document.getElementById('ch-ask-ai-styles')) return;
|
||||
var style = document.createElement('style');
|
||||
style.id = 'ch-ask-ai-styles';
|
||||
style.textContent = '.dark .ch-ai-icon { color: #fdff75; }';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Wait briefly for Kapa to mount (it's loaded async), then open. Kapa's
|
||||
// open() accepts an optional query that's submitted immediately when
|
||||
// submit:true is set.
|
||||
function openKapa(query) {
|
||||
var opts = { mode: 'ai' };
|
||||
if (query) {
|
||||
opts.query = query;
|
||||
opts.submit = true;
|
||||
}
|
||||
var attempts = 0;
|
||||
var iv = setInterval(function () {
|
||||
attempts++;
|
||||
if (window.Kapa && typeof window.Kapa.open === 'function') {
|
||||
clearInterval(iv);
|
||||
window.Kapa.open(opts);
|
||||
} else if (attempts > 60) {
|
||||
clearInterval(iv);
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function injectButton() {
|
||||
if (document.getElementById(BTN_ID)) return true;
|
||||
|
||||
var searchBar = document.getElementById('search-bar-entry');
|
||||
|
||||
if (!searchBar) return false;
|
||||
|
||||
injectStyles();
|
||||
|
||||
var btn = document.createElement('button');
|
||||
btn.id = BTN_ID;
|
||||
btn.type = 'button';
|
||||
btn.className = 'flex-none hidden lg:flex items-center justify-center gap-1.5 pl-3 pr-3.5 h-9 shadow-none bg-gray-950/[0.03] dark:bg-white/[0.03] hover:bg-gray-950/10 dark:hover:bg-white/10 border border-white/10 rounded ml-2';
|
||||
btn.setAttribute('aria-label', 'Ask AI');
|
||||
btn.innerHTML = sparkleSvg
|
||||
+ '<span class="text-sm text-gray-500 dark:text-white/50 whitespace-nowrap">Ask</span>';
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
openKapa();
|
||||
});
|
||||
|
||||
searchBar.parentNode.insertBefore(btn, searchBar.nextSibling);
|
||||
return true;
|
||||
}
|
||||
|
||||
function injectMobileButton() {
|
||||
if (document.getElementById(MOBILE_BTN_ID)) return true;
|
||||
|
||||
var mobileSearchBtn = document.getElementById('search-bar-entry-mobile');
|
||||
|
||||
if (!mobileSearchBtn) return false;
|
||||
|
||||
injectStyles();
|
||||
|
||||
var btn = document.createElement('button');
|
||||
btn.id = MOBILE_BTN_ID;
|
||||
btn.type = 'button';
|
||||
btn.className = 'text-gray-500 w-8 h-8 flex items-center justify-center hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300';
|
||||
btn.setAttribute('aria-label', 'Ask AI');
|
||||
btn.innerHTML = sparkleSvg;
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
openKapa();
|
||||
});
|
||||
|
||||
mobileSearchBtn.parentNode.insertBefore(btn, mobileSearchBtn.nextSibling);
|
||||
return true;
|
||||
}
|
||||
|
||||
function init() {
|
||||
injectButton();
|
||||
injectMobileButton();
|
||||
|
||||
var observer = new MutationObserver(function () {
|
||||
injectButton();
|
||||
injectMobileButton();
|
||||
});
|
||||
observer.observe(document.documentElement, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -1,339 +0,0 @@
|
||||
// ClickHouse-native SQL syntax highlighting for Mintlify.
|
||||
//
|
||||
// Port of clickhouse-docs' WASM highlighter (PR ClickHouse/clickhouse-docs#6308)
|
||||
// to a Mintlify custom script. Upstream swizzled Docusaurus's code-block
|
||||
// renderer; Mintlify has no equivalent, so instead we enhance the already
|
||||
// rendered DOM:
|
||||
//
|
||||
// 1. Mintlify highlights code blocks at build time with Shiki, emitting
|
||||
// `<div class="code-block" language="sql"> ... <pre class="shiki"><code>
|
||||
// <span class="line"><span style="color:...">token</span>...</span></code></pre>`.
|
||||
// 2. For blocks tagged `language="sql"` we reconstruct the source text, run it
|
||||
// through ClickHouse's own SQL lexer (compiled to WASM, the same method and
|
||||
// color scheme as `programs/server/play.html`), and replace Shiki's token
|
||||
// spans with class-based segments (`q-kw`, `q-id`, `q-fn`, ...). Because our
|
||||
// spans carry no inline `style`, the palette CSS below wins cleanly.
|
||||
//
|
||||
// Every other language is left exactly as Shiki rendered it. Re-applied on SPA
|
||||
// navigation via a MutationObserver, matching the other scripts in this folder.
|
||||
/* eslint-disable */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Numeric TokenType values, matching the order of the C++ enum in
|
||||
// src/Parsers/Lexer.h. Only the categories we classify are named.
|
||||
var TT = {
|
||||
Whitespace: 0, Comment: 1, BareWord: 2, Number: 3, StringLiteral: 4,
|
||||
QuotedIdentifier: 5, OpeningRoundBracket: 6, HereDoc: 17, DollarSign: 18,
|
||||
Plus: 19, Minus: 20, Slash: 21, Percent: 22, Arrow: 23, QuestionMark: 24,
|
||||
Colon: 25, Caret: 26, DoubleColon: 27, Equals: 28, NotEquals: 29, Less: 30,
|
||||
Greater: 31, LessOrEquals: 32, GreaterOrEquals: 33, Spaceship: 34,
|
||||
PipeMark: 35, Concatenation: 36, At: 37, DoubleAt: 38, Asterisk: 16,
|
||||
};
|
||||
|
||||
// SQL keywords recognized for highlighting. The lexer reports them as
|
||||
// BareWord, so we disambiguate identifiers from keywords here. Comparisons are
|
||||
// case-insensitive. Kept in sync with play.html / upstream highlighter.ts.
|
||||
var SQL_KEYWORDS = new Set([
|
||||
'ADD', 'AFTER', 'ALL', 'ALTER', 'AND', 'ANTI', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASCENDING',
|
||||
'ASOF', 'AST', 'ASYNC', 'ATTACH', 'BACKUP', 'BEGIN', 'BETWEEN', 'BOTH', 'BY',
|
||||
'CACHE', 'CASCADE', 'CASE', 'CAST', 'CHANGE', 'CHANGED', 'CHECK', 'CLEAR', 'CLUSTER',
|
||||
'CODEC', 'COLLATE', 'COLUMN', 'COLUMNS', 'COMMENT', 'COMMIT', 'CONSTRAINT', 'CREATE',
|
||||
'CROSS', 'CUBE', 'CURRENT',
|
||||
'DATABASE', 'DATABASES', 'DAY', 'DEDUPLICATE', 'DEFAULT', 'DELETE', 'DESC', 'DESCENDING',
|
||||
'DESCRIBE', 'DETACH', 'DICTIONARIES', 'DICTIONARY', 'DISK', 'DISTINCT', 'DISTRIBUTED',
|
||||
'DROP', 'ELSE', 'END', 'ENGINE', 'ESTIMATE', 'EVENTS', 'EXCEPT', 'EXCHANGE', 'EXISTS',
|
||||
'EXPLAIN', 'EXPRESSION', 'EXTENDED', 'EXTRACT',
|
||||
'FALSE', 'FETCH', 'FETCHES', 'FILE', 'FILESYSTEM', 'FINAL', 'FIRST', 'FLUSH', 'FOLLOWING',
|
||||
'FOR', 'FOREIGN', 'FORMAT', 'FREEZE', 'FROM', 'FULL', 'FUNCTION',
|
||||
'GLOBAL', 'GRANT', 'GROUP', 'GROUPS', 'HAVING', 'HIERARCHICAL', 'HOUR',
|
||||
'ID', 'IDENTIFIED', 'IF', 'ILIKE', 'IN', 'INDEX', 'INF', 'INHERIT', 'INJECTIVE',
|
||||
'INNER', 'INSERT', 'INTERSECT', 'INTERVAL', 'INTO', 'INVISIBLE', 'IS', 'IS_OBJECT_ID',
|
||||
'JOIN', 'KEY', 'KEYED', 'KILL',
|
||||
'LAST', 'LATERAL', 'LAYOUT', 'LEADING', 'LEFT', 'LIFETIME', 'LIKE', 'LIMIT', 'LIMITS',
|
||||
'LIVE', 'LOCAL', 'LOGS',
|
||||
'MATERIALIZE', 'MATERIALIZED', 'MAX', 'MERGES', 'MICROSECOND', 'MILLISECOND', 'MIN',
|
||||
'MINUTE', 'MODIFY', 'MONTH', 'MOVE', 'MUTATION',
|
||||
'NAN_SQL', 'NEXT', 'NO', 'NONE', 'NOT', 'NULL', 'NULLS',
|
||||
'OFFSET', 'ON', 'ONLY', 'OPTIMIZE', 'OPTION', 'OR', 'ORDER', 'OUTER', 'OUTFILE', 'OVER',
|
||||
'PARTITION', 'PASTE', 'PERMANENTLY', 'PLAN', 'POPULATE', 'PRECEDING', 'PRECISION',
|
||||
'PREWHERE', 'PRIMARY', 'PROFILE', 'PROJECTION', 'QUARTER', 'QUERY', 'QUOTA',
|
||||
'RANDOMIZED', 'RANGE', 'RECURSIVE', 'REFRESH', 'REGEXP', 'RELOAD', 'REMOTE', 'RENAME',
|
||||
'REPLACE', 'REPLICA', 'REPLICAS', 'RESET', 'RESTORE', 'RESTRICT', 'RESTRICTIVE',
|
||||
'RETURNS', 'REVOKE', 'RIGHT', 'ROLE', 'ROLLBACK', 'ROLLUP', 'ROW', 'ROWS',
|
||||
'SAMPLE', 'SECOND', 'SELECT', 'SEMI', 'SENDS', 'SET', 'SETS', 'SETTINGS', 'SHARD',
|
||||
'SHOW', 'SIGNED', 'SOURCE', 'SQL_SECURITY', 'START', 'STEP', 'STORAGE', 'STRICT',
|
||||
'STRICTLY_ASCENDING', 'SUBPARTITION', 'SUBSTRING', 'SUSPEND', 'SYNC', 'SYNTAX', 'SYSTEM',
|
||||
'TABLE', 'TABLES', 'TEMPORARY', 'TEST', 'THEN', 'TIES', 'TIMESTAMP', 'TO', 'TOP',
|
||||
'TOTALS', 'TRACKING', 'TRAILING', 'TRANSACTION', 'TRIGGER', 'TRIM', 'TRUE', 'TRUNCATE',
|
||||
'TYPE',
|
||||
'UNBOUNDED', 'UNFREEZE', 'UNION', 'UNIQUE', 'UNSIGNED', 'UPDATE', 'USE', 'USING',
|
||||
'UUID', 'VALUES', 'VARYING', 'VIEW', 'VIRTUAL', 'VISIBLE',
|
||||
'WATCH', 'WEEK', 'WHEN', 'WHERE', 'WINDOW', 'WITH', 'WORK', 'WRITABLE',
|
||||
'XOR', 'YEAR', 'ZKPATH',
|
||||
]);
|
||||
|
||||
var OPERATOR_TYPES = new Set([
|
||||
TT.Asterisk, TT.Plus, TT.Minus, TT.Slash, TT.Percent, TT.Arrow, TT.QuestionMark,
|
||||
TT.Colon, TT.DoubleColon, TT.Caret, TT.Equals, TT.NotEquals, TT.Less, TT.Greater,
|
||||
TT.LessOrEquals, TT.GreaterOrEquals, TT.Spaceship, TT.PipeMark, TT.Concatenation,
|
||||
TT.At, TT.DoubleAt, TT.DollarSign,
|
||||
]);
|
||||
|
||||
var lexerPromise = null;
|
||||
|
||||
// The WASM payload lives in a separate script (clickhouse-sql-lexer-wasm.js).
|
||||
// Mintlify loads custom scripts asynchronously, so the order in docs.json is
|
||||
// not guaranteed — wait (briefly) for the global rather than failing if this
|
||||
// script happens to run first.
|
||||
function waitForWasmGlobal() {
|
||||
if (typeof window.__CH_SQL_LEXER_WASM_B64 === 'string') {
|
||||
return Promise.resolve(window.__CH_SQL_LEXER_WASM_B64);
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
var waited = 0;
|
||||
var iv = setInterval(function () {
|
||||
if (typeof window.__CH_SQL_LEXER_WASM_B64 === 'string') {
|
||||
clearInterval(iv);
|
||||
resolve(window.__CH_SQL_LEXER_WASM_B64);
|
||||
} else if ((waited += 50) >= 10000) {
|
||||
clearInterval(iv);
|
||||
reject(new Error('ClickHouse SQL lexer WASM not loaded'));
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
}
|
||||
|
||||
// Instantiate the embedded Lexer.wasm exactly once and cache the exports.
|
||||
function loadLexer() {
|
||||
if (!lexerPromise) {
|
||||
lexerPromise = waitForWasmGlobal().then(function (b64) {
|
||||
var binary = atob(b64);
|
||||
var bytes = new Uint8Array(binary.length);
|
||||
for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
||||
return WebAssembly.instantiate(bytes).then(function (module) {
|
||||
return module.instance.exports;
|
||||
});
|
||||
}).catch(function (e) {
|
||||
lexerPromise = null; // allow a later block to retry
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
return lexerPromise;
|
||||
}
|
||||
|
||||
// Tokenize a SQL string using the ClickHouse lexer compiled to WASM.
|
||||
function tokenize(query) {
|
||||
return loadLexer().then(function (exports) {
|
||||
var queryBytes = new TextEncoder().encode(query);
|
||||
|
||||
var required = exports.clickhouse_lexer_size + queryBytes.length + 8;
|
||||
if (exports.memory.buffer.byteLength < required) {
|
||||
var pages = Math.ceil((required - exports.memory.buffer.byteLength) / 65536);
|
||||
exports.memory.grow(pages);
|
||||
}
|
||||
|
||||
var buffer = exports.memory.buffer;
|
||||
var memoryOffset = 0;
|
||||
|
||||
var lexer = memoryOffset;
|
||||
memoryOffset += exports.clickhouse_lexer_size;
|
||||
|
||||
var queryArray = new Uint8Array(buffer, memoryOffset, queryBytes.length);
|
||||
queryArray.set(queryBytes);
|
||||
var queryBegin = memoryOffset;
|
||||
memoryOffset += queryBytes.length;
|
||||
var queryEnd = memoryOffset;
|
||||
|
||||
exports.clickhouse_lexer_create(lexer, queryBegin, queryEnd, Math.max(65536, queryBytes.length));
|
||||
|
||||
var tokenBegin = memoryOffset;
|
||||
memoryOffset += 4;
|
||||
var tokenEnd = memoryOffset;
|
||||
memoryOffset += 4;
|
||||
|
||||
var view = new DataView(buffer);
|
||||
var decoder = new TextDecoder();
|
||||
var result = [];
|
||||
|
||||
while (true) {
|
||||
var tokenType = exports.clickhouse_lexer_next_token(lexer, tokenBegin, tokenEnd);
|
||||
if (
|
||||
exports.clickhouse_lexer_token_is_error(tokenType) ||
|
||||
exports.clickhouse_lexer_token_is_end(tokenType)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
var begin = view.getUint32(tokenBegin, true);
|
||||
var end = view.getUint32(tokenEnd, true);
|
||||
var token = decoder.decode(new Uint8Array(buffer, begin, end - begin));
|
||||
result.push({ type: tokenType, token: token });
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
// Map a single token to a CSS class. For BareWords we peek at the next
|
||||
// non-whitespace token to distinguish a function call (`foo(`) from a plain
|
||||
// identifier — the lexer alone cannot tell them apart.
|
||||
function tokenClass(tokens, i) {
|
||||
var elem = tokens[i];
|
||||
switch (elem.type) {
|
||||
case TT.Comment: return 'q-com';
|
||||
case TT.Number: return 'q-num';
|
||||
case TT.StringLiteral:
|
||||
case TT.HereDoc: return 'q-str';
|
||||
case TT.QuotedIdentifier: return 'q-qid';
|
||||
case TT.BareWord: {
|
||||
if (SQL_KEYWORDS.has(elem.token.toUpperCase())) return 'q-kw';
|
||||
for (var j = i + 1; j < tokens.length; ++j) {
|
||||
if (tokens[j].type === TT.Whitespace) continue;
|
||||
return tokens[j].type === TT.OpeningRoundBracket ? 'q-fn' : 'q-id';
|
||||
}
|
||||
return 'q-id';
|
||||
}
|
||||
default:
|
||||
return OPERATOR_TYPES.has(elem.type) ? 'q-op' : '';
|
||||
}
|
||||
}
|
||||
|
||||
// Append `text` (which may contain newlines) to `lines`, starting a new line
|
||||
// for each `\n`.
|
||||
function pushSegment(lines, text, className) {
|
||||
var parts = text.split('\n');
|
||||
for (var j = 0; j < parts.length; j++) {
|
||||
if (j > 0) lines.push([]);
|
||||
if (parts[j].length > 0) {
|
||||
lines[lines.length - 1].push({ text: parts[j], className: className });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tokenize `code` and return it as an array of lines, where each line is an
|
||||
// array of styled segments.
|
||||
function buildHighlightedLines(code) {
|
||||
return tokenize(code).then(function (tokens) {
|
||||
var lines = [[]];
|
||||
var consumed = 0;
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var text = tokens[i].token;
|
||||
consumed += text.length;
|
||||
pushSegment(lines, text, tokenClass(tokens, i));
|
||||
}
|
||||
// Any tail not covered by tokens (lexer error / size limit) is rendered
|
||||
// plain rather than as an error: docs SQL blocks may be followed by
|
||||
// non-SQL text. Plain output keeps the block readable.
|
||||
if (consumed < code.length) {
|
||||
pushSegment(lines, code.slice(consumed), '');
|
||||
}
|
||||
return lines;
|
||||
});
|
||||
}
|
||||
|
||||
// ---- DOM integration ------------------------------------------------------
|
||||
|
||||
var STYLE_ID = 'ch-sql-highlight-styles';
|
||||
|
||||
// The exact light + dark palette from play.html / clickhouse-client, adapted
|
||||
// to Mintlify's dark-mode carrier (`<html class="dark">`). Scoped to
|
||||
// `.ch-sql-hl` so only blocks we rebuilt are affected. `!important` is
|
||||
// required because Mintlify forces `html.dark .shiki span { color:
|
||||
// var(--shiki-dark) !important }` on every token; our higher-specificity
|
||||
// `.dark .ch-sql-hl .q-*` rules only win when they are also `!important`.
|
||||
function injectStyles() {
|
||||
if (document.getElementById(STYLE_ID)) return;
|
||||
var css =
|
||||
'.ch-sql-hl .q-kw{font-weight:bold !important}' +
|
||||
'.ch-sql-hl .q-com{font-style:italic !important;color:#757575 !important}' +
|
||||
'.ch-sql-hl .q-id{color:#00838f !important}' +
|
||||
'.ch-sql-hl .q-fn{color:#875f00 !important}' +
|
||||
'.ch-sql-hl .q-num{color:#008700 !important}' +
|
||||
'.ch-sql-hl .q-str{color:#006400 !important}' +
|
||||
'.ch-sql-hl .q-qid{color:#008b8b !important}' +
|
||||
'.dark .ch-sql-hl .q-id{color:#00cdcd !important}' +
|
||||
'.dark .ch-sql-hl .q-fn{color:#cdcd00 !important}' +
|
||||
'.dark .ch-sql-hl .q-num{color:#00d700 !important}' +
|
||||
'.dark .ch-sql-hl .q-str{color:#00cd00 !important}' +
|
||||
'.dark .ch-sql-hl .q-qid{color:#00d7d7 !important}' +
|
||||
'.dark .ch-sql-hl .q-com{color:#9e9e9e !important}';
|
||||
var style = document.createElement('style');
|
||||
style.id = STYLE_ID;
|
||||
style.textContent = css;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Replace a line element's Shiki token spans with our class-based segments.
|
||||
// Uses textContent (not innerHTML) so SQL characters like `<` and `&` are
|
||||
// never interpreted as markup.
|
||||
function renderLine(lineEl, segments) {
|
||||
while (lineEl.firstChild) lineEl.removeChild(lineEl.firstChild);
|
||||
if (segments.length === 0) return;
|
||||
for (var i = 0; i < segments.length; i++) {
|
||||
var seg = segments[i];
|
||||
if (seg.className) {
|
||||
var span = document.createElement('span');
|
||||
span.className = seg.className;
|
||||
span.textContent = seg.text;
|
||||
lineEl.appendChild(span);
|
||||
} else {
|
||||
lineEl.appendChild(document.createTextNode(seg.text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function enhanceBlock(block) {
|
||||
var code = block.querySelector('pre.shiki code');
|
||||
if (!code) return;
|
||||
if (code.dataset.chSqlState) return; // already processed / in flight / failed
|
||||
|
||||
var lineEls = code.querySelectorAll(':scope > span.line');
|
||||
if (lineEls.length === 0) return;
|
||||
|
||||
var source = Array.prototype.map.call(lineEls, function (l) {
|
||||
return l.textContent;
|
||||
}).join('\n');
|
||||
|
||||
code.dataset.chSqlState = 'pending';
|
||||
|
||||
buildHighlightedLines(source).then(function (lines) {
|
||||
// Only rebuild when the line structure matches exactly; otherwise leave
|
||||
// Shiki's output untouched to avoid corrupting the block.
|
||||
if (lines.length !== lineEls.length) {
|
||||
code.dataset.chSqlState = 'skipped';
|
||||
return;
|
||||
}
|
||||
injectStyles();
|
||||
for (var i = 0; i < lineEls.length; i++) renderLine(lineEls[i], lines[i]);
|
||||
code.classList.add('ch-sql-hl');
|
||||
code.dataset.chSqlState = 'done';
|
||||
}).catch(function (e) {
|
||||
code.dataset.chSqlState = 'failed';
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('ClickHouse SQL highlighting failed:', e);
|
||||
});
|
||||
}
|
||||
|
||||
function scan() {
|
||||
var blocks = document.querySelectorAll('.code-block[language="sql"]');
|
||||
for (var i = 0; i < blocks.length; i++) enhanceBlock(blocks[i]);
|
||||
}
|
||||
|
||||
function init() {
|
||||
scan();
|
||||
// Re-scan on SPA navigation / late renders. Our own edits set chSqlState so
|
||||
// already-handled blocks are skipped, preventing an observer feedback loop.
|
||||
var scheduled = false;
|
||||
var observer = new MutationObserver(function () {
|
||||
if (scheduled) return;
|
||||
scheduled = true;
|
||||
requestAnimationFrame(function () { scheduled = false; scan(); });
|
||||
});
|
||||
observer.observe(document.documentElement, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -1,83 +0,0 @@
|
||||
// ClickHouse SQL lexer (src/Parsers/Lexer.cpp) compiled to WebAssembly,
|
||||
// extracted verbatim from ClickHouse's programs/server/play.html.
|
||||
// Ported from clickhouse-docs src/components/CodeViewer/clickhouse-sql/lexerWasm.ts
|
||||
// (PR ClickHouse/clickhouse-docs#6308). Exposes the base64 payload as a global
|
||||
// consumed by clickhouse-sql-highlight.js. If ClickHouse's lexer/keyword list
|
||||
// changes upstream, re-extract this base64 and sync the keyword set.
|
||||
/* eslint-disable */
|
||||
window.__CH_SQL_LEXER_WASM_B64 =
|
||||
"AGFzbQEAAAABHAVgAX8Bf2ACf38AYAAAYAR/f39/AGADf39/AX8DCQgCAQEDBAAAAAUDAQACBkULfwFBkIgEC38AQYAIC38AQYAIC38AQYQIC38AQZAIC38A" +
|
||||
"QZCIBAt/AEGACAt/AEGQiAQLfwBBgIAIC38AQQALfwBBAQsHlgMTBm1lbW9yeQIAEV9fd2FzbV9jYWxsX2N0b3JzAAAYX1pOMkRCNUxleGVyOW5leHRUb2tl" +
|
||||
"bkV2AAEdX1pOMkRCNUxleGVyMTNuZXh0VG9rZW5JbXBsRXYAAhdjbGlja2hvdXNlX2xleGVyX2NyZWF0ZQADG2NsaWNraG91c2VfbGV4ZXJfbmV4dF90b2tl" +
|
||||
"bgAEJWNsaWNraG91c2VfbGV4ZXJfdG9rZW5faXNfc2lnbmlmaWNhbnQABR9jbGlja2hvdXNlX2xleGVyX3Rva2VuX2lzX2Vycm9yAAYdY2xpY2tob3VzZV9s" +
|
||||
"ZXhlcl90b2tlbl9pc19lbmQABxVjbGlja2hvdXNlX2xleGVyX3NpemUDAQxfX2Rzb19oYW5kbGUDAgpfX2RhdGFfZW5kAwMLX19zdGFja19sb3cDBAxfX3N0" +
|
||||
"YWNrX2hpZ2gDBQ1fX2dsb2JhbF9iYXNlAwYLX19oZWFwX2Jhc2UDBwpfX2hlYXBfZW5kAwgNX19tZW1vcnlfYmFzZQMJDF9fdGFibGVfYmFzZQMKDAEBCogu" +
|
||||
"CAMAAQtNAQF/IAAgARACAkACQAJAIAEoAgwiAkUNACAAKAIIIAEoAgAgAmpNDQBBMCECIABBMDoAAAwBCyAALQAAIgJBAkkNAQsgASACOgAQCwu+LAELfyAB" +
|
||||
"KAIEIgQgASgCCCIDTwRAIAAgAzYCCCAAIAM2AgQgAEEnOgAADwsgAUEEaiEGAkACQAJAAkACQAJAAkACQAJAAkACQAJAAn8CQAJAAkACQAJAAkACQAJAAkAC" +
|
||||
"QAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAELQAAIgpBIGsOXgEWBBMgFCQDBgcRDwwQDhIC" +
|
||||
"AgICAgICAgICGw0XFRgZHSQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkCB4JGiQFJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQKHAsACyAKQQlrQQVJDQAg" +
|
||||
"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" +
|
||||
"LXR5cGVzKwptdWx0aXZhbHVl";
|
||||
File diff suppressed because one or more lines are too long
@@ -1,384 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Mintlify adapter for clickhouse-docs/src/lib/galaxy. Keep the event
|
||||
// envelope and transport compatible with the legacy Docusaurus site so
|
||||
// existing dashboards continue to receive the same data.
|
||||
var APPLICATION = 'DOCS_WEBSITE';
|
||||
var isLocal = /^(localhost|127\.0\.0\.1|\[?::1\]?)$/
|
||||
.test(window.location.hostname || '');
|
||||
var isMintlifyPreview = /\.mintlify\.app$/
|
||||
.test(window.location.hostname || '');
|
||||
var isCanonicalDocs = window.location.origin === 'https://clickhouse.com' &&
|
||||
/^\/docs(?:\/|$)/.test(window.location.pathname);
|
||||
var API_HOST = null;
|
||||
if (!isLocal && isMintlifyPreview) {
|
||||
API_HOST = 'https://control-plane-internal.clickhouse-dev.com';
|
||||
} else if (isCanonicalDocs) {
|
||||
API_HOST = 'https://control-plane-internal.clickhouse.cloud';
|
||||
}
|
||||
var API_PATH = '/api/galaxy?sendGalaxyForensicEvent';
|
||||
var USER_ID_KEY = 'glx_anonymous_id';
|
||||
var SESSION_ID_KEY = 'glx_id';
|
||||
var MAX_COOKIE_AGE = 2147483647;
|
||||
var FLUSH_INTERVAL_MS = 5000;
|
||||
var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
|
||||
var ATTRIBUTION_HOSTS = ['clickhouse.cloud', 'console.clickhouse.cloud'];
|
||||
|
||||
if (window.__clickhouseGalaxyInitialized) return;
|
||||
window.__clickhouseGalaxyInitialized = true;
|
||||
|
||||
var eventsQueue = [];
|
||||
var flushInFlight = null;
|
||||
|
||||
function uuid() {
|
||||
if (window.crypto && typeof window.crypto.randomUUID === 'function') {
|
||||
return window.crypto.randomUUID();
|
||||
}
|
||||
|
||||
// randomUUID is available in current browsers. This fallback keeps local
|
||||
// previews and older embedded browsers functional without adding a bundle
|
||||
// dependency solely for UUID generation.
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r;
|
||||
if (window.crypto && typeof window.crypto.getRandomValues === 'function') {
|
||||
var value = new Uint8Array(1);
|
||||
window.crypto.getRandomValues(value);
|
||||
r = value[0] & 15;
|
||||
} else {
|
||||
r = Math.floor(Math.random() * 16);
|
||||
}
|
||||
return (c === 'x' ? r : (r & 3) | 8).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
var prefix = encodeURIComponent(name) + '=';
|
||||
var cookies = document.cookie ? document.cookie.split(';') : [];
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = cookies[i].trim();
|
||||
if (cookie.indexOf(prefix) === 0) {
|
||||
try {
|
||||
return decodeURIComponent(cookie.slice(prefix.length));
|
||||
} catch (e) {
|
||||
return cookie.slice(prefix.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function storageGet(storage, key) {
|
||||
try {
|
||||
return storage.getItem(key);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function storageSet(storage, key, value) {
|
||||
try {
|
||||
storage.setItem(key, value);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function persistUserId(value) {
|
||||
try {
|
||||
var cookie = encodeURIComponent(USER_ID_KEY) + '=' + encodeURIComponent(value) +
|
||||
'; path=/; max-age=' + MAX_COOKIE_AGE;
|
||||
if (window.location.protocol === 'https:') cookie += '; secure';
|
||||
document.cookie = cookie;
|
||||
} catch (e) {}
|
||||
|
||||
storageSet(window.localStorage, USER_ID_KEY, value);
|
||||
storageSet(window.sessionStorage, USER_ID_KEY, value);
|
||||
}
|
||||
|
||||
function getUserId() {
|
||||
var id = getCookie(USER_ID_KEY) ||
|
||||
storageGet(window.localStorage, USER_ID_KEY) ||
|
||||
storageGet(window.sessionStorage, USER_ID_KEY) ||
|
||||
uuid();
|
||||
persistUserId(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
function getSessionId() {
|
||||
var id = storageGet(window.sessionStorage, SESSION_ID_KEY);
|
||||
if (!id) {
|
||||
id = uuid();
|
||||
storageSet(window.sessionStorage, SESSION_ID_KEY, id);
|
||||
}
|
||||
return id || 'unknown';
|
||||
}
|
||||
|
||||
function getContext() {
|
||||
return {
|
||||
application: APPLICATION,
|
||||
page: window.location.href,
|
||||
userAgent: window.navigator.userAgent,
|
||||
};
|
||||
}
|
||||
|
||||
function track(event, properties) {
|
||||
if (!event) return;
|
||||
|
||||
var eventProperties = properties || { interaction: 'click' };
|
||||
var interaction = eventProperties.interaction;
|
||||
var extraProperties = {};
|
||||
for (var key in eventProperties) {
|
||||
if (Object.prototype.hasOwnProperty.call(eventProperties, key) && key !== 'interaction') {
|
||||
extraProperties[key] = eventProperties[key];
|
||||
}
|
||||
}
|
||||
|
||||
var parts = String(event).split('.');
|
||||
var payloadProperties = getContext();
|
||||
eventsQueue.push({
|
||||
application: APPLICATION,
|
||||
timestamp: Date.now(),
|
||||
userId: getUserId(),
|
||||
namespace: parts[0],
|
||||
component: parts[1],
|
||||
interaction: interaction,
|
||||
orgId: payloadProperties.orgId,
|
||||
event: parts[2],
|
||||
message: parts[2],
|
||||
properties: Object.assign({ properties: payloadProperties }, extraProperties),
|
||||
});
|
||||
}
|
||||
|
||||
function post(url, requestBody, useBeacon) {
|
||||
var json = JSON.stringify(requestBody);
|
||||
var blob = new Blob([json], { type: 'application/json;charset=UTF-8' });
|
||||
var tooLarge = blob.size > KEEPALIVE_LIMIT_BYTES;
|
||||
|
||||
if (useBeacon && !tooLarge && typeof window.navigator.sendBeacon === 'function') {
|
||||
try {
|
||||
if (window.navigator.sendBeacon(url, blob)) return Promise.resolve();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return window.fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
|
||||
body: json,
|
||||
keepalive: !tooLarge,
|
||||
}).then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Galaxy request failed with status ' + response.status);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function flushEvents(useBeacon) {
|
||||
if (flushInFlight || eventsQueue.length === 0) {
|
||||
return flushInFlight || Promise.resolve();
|
||||
}
|
||||
|
||||
var eventCount = eventsQueue.length;
|
||||
if (!API_HOST) {
|
||||
eventsQueue.splice(0, eventCount);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var request = {
|
||||
rpcAction: 'sendGalaxyForensicEvent',
|
||||
galaxySessionId: getSessionId(),
|
||||
data: eventsQueue.slice(0, eventCount),
|
||||
};
|
||||
|
||||
flushInFlight = post(API_HOST + API_PATH, request, useBeacon === true)
|
||||
.then(function () {
|
||||
eventsQueue.splice(0, eventCount);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error('Could not log to galaxy', error);
|
||||
})
|
||||
.then(function () {
|
||||
flushInFlight = null;
|
||||
});
|
||||
|
||||
return flushInFlight;
|
||||
}
|
||||
|
||||
window.galaxy = {
|
||||
track: track,
|
||||
flushEvents: flushEvents,
|
||||
};
|
||||
|
||||
window.dispatchEvent(new Event('galaxy:ready'));
|
||||
|
||||
var flushInterval = window.setInterval(flushEvents, FLUSH_INTERVAL_MS);
|
||||
|
||||
function stopGalaxy() {
|
||||
window.clearInterval(flushInterval);
|
||||
void flushEvents(true);
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', stopGalaxy);
|
||||
window.addEventListener('pagehide', function (event) {
|
||||
// Timers are suspended while a page is in the back/forward cache and
|
||||
// resume when it is restored. Clearing the interval here would leave a
|
||||
// restored page queueing events without ever flushing them.
|
||||
if (!event.persisted) stopGalaxy();
|
||||
});
|
||||
|
||||
function trackPageEvent(action) {
|
||||
track('docs.window.' + action, {
|
||||
interaction: 'trigger',
|
||||
});
|
||||
}
|
||||
|
||||
// Docusaurus hooks tracked load, focus, and blur for every docs and KB page.
|
||||
// Mintlify is a SPA, so watch the path to emit one load event per route.
|
||||
var lastPath = window.location.pathname;
|
||||
trackPageEvent('load');
|
||||
window.addEventListener('focus', function () { trackPageEvent('focus'); });
|
||||
window.addEventListener('blur', function () { trackPageEvent('blur'); });
|
||||
|
||||
// Track clicks declaratively from MDX, including clicks on descendants of
|
||||
// wrappers around Mintlify components:
|
||||
// <div data-galaxy-event="docs.install.download"
|
||||
// data-galaxy-prop-os="linux">...</div>
|
||||
function getTrackedHref(anchor) {
|
||||
var href = anchor.getAttribute('href');
|
||||
// Homepage click handlers add the canonical /docs base before navigating.
|
||||
// Record that final destination rather than the unprefixed DOM attribute.
|
||||
if (isCanonicalDocs && href && href.charAt(0) === '/' &&
|
||||
href.indexOf('//') !== 0 && !/^\/docs(?:\/|$)/.test(href)) {
|
||||
return '/docs' + href;
|
||||
}
|
||||
return href;
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var target = event.target;
|
||||
if (!target || typeof target.closest !== 'function') return;
|
||||
|
||||
var element = target.closest('[data-galaxy-event]');
|
||||
if (!element) return;
|
||||
|
||||
var eventName = element.getAttribute('data-galaxy-event');
|
||||
if (!eventName) return;
|
||||
|
||||
var properties = { interaction: 'click' };
|
||||
var anchor = target.closest('a[href]') ||
|
||||
(element.tagName === 'A' ? element : element.querySelector('a[href]'));
|
||||
if (anchor) properties.href = getTrackedHref(anchor);
|
||||
|
||||
for (var i = 0; i < element.attributes.length; i++) {
|
||||
var attribute = element.attributes[i];
|
||||
if (attribute.name.indexOf('data-galaxy-prop-') === 0) {
|
||||
properties[attribute.name.slice('data-galaxy-prop-'.length)] = attribute.value;
|
||||
}
|
||||
}
|
||||
|
||||
track(eventName, properties);
|
||||
}, true);
|
||||
|
||||
function watchPath() {
|
||||
var path = window.location.pathname;
|
||||
if (path !== lastPath) {
|
||||
lastPath = path;
|
||||
trackPageEvent('load');
|
||||
updateCloudLinks();
|
||||
}
|
||||
window.requestAnimationFrame(watchPath);
|
||||
}
|
||||
window.requestAnimationFrame(watchPath);
|
||||
|
||||
// Preserve the attribution behavior from src/clientModules/utmPersistence:
|
||||
// retain campaign parameters and attach the Galaxy ID and page paths to
|
||||
// links into ClickHouse Cloud.
|
||||
function saveAttribution() {
|
||||
try {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var values = {};
|
||||
params.forEach(function (value, key) {
|
||||
if (key.indexOf('utm_') === 0 || key === 'gclid') values[key] = value;
|
||||
});
|
||||
if (Object.keys(values).length > 0) {
|
||||
var expiration = new Date();
|
||||
expiration.setDate(expiration.getDate() + 14);
|
||||
window.localStorage.setItem('ch-utms', JSON.stringify({
|
||||
data: values,
|
||||
timestamp: expiration.getTime(),
|
||||
}));
|
||||
}
|
||||
if (!window.localStorage.getItem('origPath')) {
|
||||
window.localStorage.setItem('origPath', window.location.pathname);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function storedAttribution() {
|
||||
try {
|
||||
var stored = window.localStorage.getItem('ch-utms');
|
||||
if (!stored) return null;
|
||||
var parsed = JSON.parse(stored);
|
||||
if (Date.now() > Number(parsed.timestamp)) {
|
||||
window.localStorage.removeItem('ch-utms');
|
||||
return null;
|
||||
}
|
||||
return parsed.data;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function googleAnalyticsId() {
|
||||
return getCookie('_ga');
|
||||
}
|
||||
|
||||
function updateCloudLinks() {
|
||||
if (!isCanonicalDocs) return;
|
||||
|
||||
saveAttribution();
|
||||
var attribution = storedAttribution();
|
||||
var links = document.querySelectorAll('a[href*="clickhouse.cloud"]');
|
||||
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
try {
|
||||
var url = new URL(links[i].href, window.location.href);
|
||||
if (ATTRIBUTION_HOSTS.indexOf(url.hostname) === -1) continue;
|
||||
|
||||
if (attribution) {
|
||||
for (var key in attribution) {
|
||||
if (Object.prototype.hasOwnProperty.call(attribution, key)) {
|
||||
url.searchParams.set(key, attribution[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
url.searchParams.set('glxid', getUserId());
|
||||
url.searchParams.set('pagePath', window.location.pathname);
|
||||
|
||||
var originalPath = storageGet(window.localStorage, 'origPath');
|
||||
if (originalPath) url.searchParams.set('origPath', originalPath);
|
||||
|
||||
var gaId = googleAnalyticsId();
|
||||
if (gaId) url.searchParams.set('utm_ga', gaId);
|
||||
|
||||
links[i].href = url.toString();
|
||||
} catch (e) {
|
||||
console.warn('Failed to update cloud link', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startLinkObserver() {
|
||||
if (!isCanonicalDocs) return;
|
||||
|
||||
updateCloudLinks();
|
||||
new MutationObserver(updateCloudLinks).observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', startLinkObserver);
|
||||
} else {
|
||||
startLinkObserver();
|
||||
}
|
||||
})();
|
||||
@@ -1,278 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Inkeep search integration (official @inkeep/cxkit-mintlify package).
|
||||
//
|
||||
// Replaces Mintlify's native search with Inkeep's search-only modal. The
|
||||
// cxkit-mintlify bundle automatically wires itself to Mintlify's search
|
||||
// entry points — it opens the modal on:
|
||||
// • clicks on #search-bar-entry / #search-bar-entry-mobile (navbar), and
|
||||
// #home-search-entry (the home page hero button, see index.mdx)
|
||||
// • the ⌘K / Ctrl+K hotkey
|
||||
//
|
||||
// We use Inkeep.ModalSearchAndChat — it is the ONLY component that installs
|
||||
// the native-search auto-hook (ModalSearch/SearchBar do not) — and force it
|
||||
// search-only with `defaultView: 'search'` + `canToggleView: false`. That
|
||||
// hides the chat toggle and the "Ask AI" card, so Inkeep does search only;
|
||||
// "Ask AI" is handled by Kapa (see kapa-init.js).
|
||||
|
||||
// Inkeep integration API keys. These are client-side/public keys (they ship
|
||||
// in the browser bundle), so committing them is expected. Staging covers any
|
||||
// *.mintlify.app host (preview/staging deploys); every other host (localhost,
|
||||
// mint dev, a future prod domain) uses the local-dev key.
|
||||
var INKEEP_API_KEY_STAGING = 'd3e2792740610240ff7bcf2c2a78a33012812eb4f3e34d54';
|
||||
var INKEEP_API_KEY_LOCAL = 'b25e5cf856ec9da60d250578b59dace8417359feeedcbc6b';
|
||||
var INKEEP_API_KEY = /\.mintlify\.app$/.test(window.location.hostname)
|
||||
? INKEEP_API_KEY_STAGING
|
||||
: INKEEP_API_KEY_LOCAL;
|
||||
|
||||
// cxkit-mintlify CDN bundle. @0.5 resolves to the latest 0.5.x; pin a full
|
||||
// version (e.g. @0.5.119) when deploying for reproducible builds.
|
||||
var INKEEP_SCRIPT_URL = 'https://cdn.jsdelivr.net/npm/@inkeep/[email protected]/dist/index.js';
|
||||
|
||||
// ── Search tabs ──────────────────────────────────────────────────────────
|
||||
// Row 1 (always visible): the top-level tabs. 'Docs' is first because cxkit
|
||||
// uses the FIRST configured tab as the default selected one (its defaultTab
|
||||
// is derived as `tabs[0]`), so the modal opens on Docs results.
|
||||
//
|
||||
// There is deliberately NO catch-all tab. If one is ever reintroduced, it
|
||||
// must NOT be named 'All': Inkeep reserves the literal 'All' as a built-in
|
||||
// tab that force-pushes EVERY result into it (`tabs.includes("All") &&
|
||||
// bucket.All.push(item)` in the cxkit bundle), which can't be filtered — so
|
||||
// changelogs could never be excluded from it.
|
||||
var ROW1_TABS = ['Docs', 'Changelogs', 'Blogs', 'Website', 'GitHub'];
|
||||
// Row 2 (docs sub-areas): shown only while a docs-context tab is active. A
|
||||
// docs source is tagged with BOTH 'Docs' and its sub-area, so selecting
|
||||
// 'Docs' shows everything and selecting a sub-area narrows it.
|
||||
var DOCS_SUBAREAS = [
|
||||
'Get started', 'Concepts', 'Guides', 'Reference',
|
||||
'Cloud', 'ClickHouse Private', 'Managed Postgres', 'ClickStack',
|
||||
'Agentic Data Stack', 'chDB', 'Kubernetes Operator',
|
||||
'ClickPipes', 'Connectors', 'Language clients', 'Ecosystem',
|
||||
];
|
||||
var SEARCH_TABS = ROW1_TABS.concat(DOCS_SUBAREAS);
|
||||
|
||||
// Maps a docs path (segment after /docs/) to its row-2 sub-area. Ordered:
|
||||
// first matching prefix wins, so specific paths precede their parent.
|
||||
var DOCS_SUBAREA_RULES = [
|
||||
['get-started', 'Get started'],
|
||||
['concepts', 'Concepts'],
|
||||
['guides', 'Guides'],
|
||||
['reference', 'Reference'],
|
||||
['products/cloud', 'Cloud'],
|
||||
['products/bring-your-own-cloud', 'Cloud'],
|
||||
['products/clickhouse-private', 'ClickHouse Private'],
|
||||
['products/managed-postgres', 'Managed Postgres'],
|
||||
['products/agentic-data-stack', 'Agentic Data Stack'],
|
||||
['chdb', 'chDB'],
|
||||
['products/kubernetes-operator', 'Kubernetes Operator'],
|
||||
['clickstack', 'ClickStack'],
|
||||
['integrations/clickpipes', 'ClickPipes'],
|
||||
['integrations/connectors', 'Connectors'],
|
||||
['integrations/language-clients', 'Language clients'],
|
||||
['integrations', 'Ecosystem'],
|
||||
];
|
||||
|
||||
function docsSubAreaTab(canonicalDocsUrl) {
|
||||
var path = canonicalDocsUrl
|
||||
.replace(/^https?:\/\/clickhouse\.com\/docs\//, '')
|
||||
.replace(/[?#].*$/, '');
|
||||
for (var i = 0; i < DOCS_SUBAREA_RULES.length; i++) {
|
||||
var prefix = DOCS_SUBAREA_RULES[i][0];
|
||||
if (path === prefix || path.indexOf(prefix + '/') === 0) return DOCS_SUBAREA_RULES[i][1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// CSS (injected into the Inkeep shadow root via theme.styles) that turns the
|
||||
// flat tab row into two rows: row 1 always visible, the docs sub-areas only
|
||||
// while 'Docs' or one of those sub-areas is the active tab. Tabs are matched
|
||||
// by their stable id suffix (`...-trigger-<TabName>`); a sub-area tab is
|
||||
// simply "any tab that is not a row-1 tab", so adding sub-areas needs no CSS
|
||||
// change. data-state + :has drive the conditional visibility (both verified
|
||||
// supported in the cxkit shadow root).
|
||||
function buildTwoRowTabCss() {
|
||||
var LIST = '.ikp-ai-search-results__tab-list';
|
||||
var TAB = '.ikp-ai-search-results__tab';
|
||||
var notRow1 = ROW1_TABS.map(function (n) { return ':not([id$="-trigger-' + n + '"])'; }).join('');
|
||||
var SUB = TAB + notRow1; // any docs sub-area tab
|
||||
var row1 = TAB + ':not(' + SUB.slice(TAB.length) + ')'; // its complement (row-1 tabs)
|
||||
var docsActive = LIST + ':has(' + TAB + '[id$="-trigger-Docs"][data-state="active"])';
|
||||
var subActive = LIST + ':has(' + SUB + '[data-state="active"])';
|
||||
return [
|
||||
// Let the row wrap and stop the single-line horizontal scroll.
|
||||
LIST + ' { flex-wrap: wrap !important; overflow-x: visible !important; row-gap: 0.375rem; }',
|
||||
// Force ordering: row-1 tabs, then a break, then the sub-area tabs.
|
||||
row1 + ' { order: 0; }',
|
||||
SUB + ' { order: 2; display: none !important; }',
|
||||
// Reveal the sub-area tabs only when Docs (or a sub-area) is active.
|
||||
docsActive + ' ' + SUB + ', ' + subActive + ' ' + SUB + ' { display: inline-flex !important; }',
|
||||
// A full-width pseudo-element (a flex item of the tab-list) ordered
|
||||
// between row-1 and the sub-areas forces the sub-areas onto their own
|
||||
// second line — only while that row is showing. No DOM injection, so it
|
||||
// survives Inkeep's re-renders.
|
||||
docsActive + '::before, ' + subActive + '::before' +
|
||||
' { content: ""; order: 1; flex: 0 0 100%; height: 0; }',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function loadScript(url, callback) {
|
||||
if (document.getElementById('inkeep-cxkit-script')) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
var script = document.createElement('script');
|
||||
script.id = 'inkeep-cxkit-script';
|
||||
script.src = url;
|
||||
script.type = 'text/javascript';
|
||||
script.onload = callback;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
// When the Inkeep modal opens, its scroll lock (react-remove-scroll) marks
|
||||
// the body with [data-scroll-locked] and adds `padding-right` equal to the
|
||||
// scrollbar width to "compensate" for hiding the scrollbar. On systems with
|
||||
// classic (non-overlay) scrollbars that padding shrinks the body and shifts
|
||||
// centered page content sideways. We neutralize it — the selector is more
|
||||
// specific than react-remove-scroll's own rule so it wins regardless of
|
||||
// injection order.
|
||||
function injectNoShiftStyle() {
|
||||
if (document.getElementById('inkeep-no-shift-style')) return;
|
||||
var style = document.createElement('style');
|
||||
style.id = 'inkeep-no-shift-style';
|
||||
style.textContent =
|
||||
'html body[data-scroll-locked] { padding-right: 0 !important; margin-right: 0 !important; }';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function initInkeep() {
|
||||
if (typeof Inkeep === 'undefined' || !Inkeep || typeof Inkeep.ModalSearchAndChat !== 'function') {
|
||||
console.log('Inkeep: cxkit-mintlify did not expose ModalSearchAndChat.');
|
||||
return;
|
||||
}
|
||||
|
||||
var settings = {
|
||||
// Open straight to the search view. (canToggleView is honored by
|
||||
// SearchBar/ChatButton but NOT by ModalSearchAndChat, so we hide the
|
||||
// chat affordances via CSS below instead.)
|
||||
defaultView: 'search',
|
||||
baseSettings: {
|
||||
apiKey: INKEEP_API_KEY,
|
||||
primaryBrandColor: '#fdff75',
|
||||
organizationDisplayName: 'ClickHouse',
|
||||
// Route each search result into a custom tab by its URL. The cxkit
|
||||
// `tabs` array below (in searchSettings) controls which tabs render and
|
||||
// their order. Docs results are served from the Mintlify preview host,
|
||||
// so we also rewrite those links to the canonical clickhouse.com/docs.
|
||||
// IMPORTANT: this must be idempotent. Inkeep re-invokes transformSource
|
||||
// on already-transformed sources (the incoming source.tabs/url are what
|
||||
// we returned last time), so we must NOT append to source.tabs or
|
||||
// re-derive from a URL we already rewrote. We rebuild the tabs array
|
||||
// from scratch off the URL each call — recognizing both the raw preview
|
||||
// host and the already rewritten clickhouse.com/docs form — and
|
||||
// overwrite tabs with the result.
|
||||
transformSource: function (source) {
|
||||
var url = source.url || '';
|
||||
var isPreview = url.indexOf('private-7c7dfe99.mintlify.app') !== -1;
|
||||
if (isPreview) {
|
||||
// Rewrite preview links to the canonical clickhouse.com/docs domain.
|
||||
url = url.replace(/^https?:\/\/private-7c7dfe99\.mintlify\.app\//, 'https://clickhouse.com/docs/');
|
||||
}
|
||||
// Do NOT tag sources with 'All' — 'All' is Inkeep's reserved built-in
|
||||
// tab (shows every result automatically); tagging sources with it
|
||||
// corrupts per-tab filtering. Docs sources are multi-tab (Docs + the
|
||||
// row-2 sub-area) so 'Docs' shows all docs and a sub-area narrows.
|
||||
var tabs = [];
|
||||
if (isPreview || /clickhouse\.com\/docs(\/|$)/.test(url)) {
|
||||
if (/\/resources\/changelogs(\/|$)/.test(url)) {
|
||||
tabs.push('Changelogs');
|
||||
} else {
|
||||
tabs.push('Docs');
|
||||
var sub = docsSubAreaTab(url);
|
||||
if (sub) tabs.push(sub);
|
||||
}
|
||||
} else if (url.indexOf('github.com') !== -1 && /\/issues(\/|$)/.test(url)) {
|
||||
tabs.push('GitHub'); // GitHub issues only — exclude repos, PRs, etc.
|
||||
} else if (/\/blog(\/|$)/.test(url)) {
|
||||
tabs.push('Blogs');
|
||||
} else if (url.indexOf('clickhouse.com') !== -1) {
|
||||
tabs.push('Website'); // marketing site (the /docs case is handled above)
|
||||
}
|
||||
// No catch-all tab: a result that matched none of the branches above
|
||||
// (tabs still empty) appears in no tab, which is deliberate — every
|
||||
// result must earn a place in one of the named tabs.
|
||||
return Object.assign({}, source, { tabs: tabs, url: url });
|
||||
},
|
||||
// Follow Mintlify's `.dark` class on <html>.
|
||||
colorMode: {
|
||||
sync: {
|
||||
target: document.documentElement,
|
||||
attributes: ['class'],
|
||||
isDarkMode: function (attributes) {
|
||||
return ((attributes && attributes.class) || '').indexOf('dark') !== -1;
|
||||
},
|
||||
},
|
||||
},
|
||||
// Make the modal search-only: hide the Search/Ask AI view toggle and
|
||||
// the "Ask AI — Start conversation" card. Inkeep renders inside a
|
||||
// shadow root, so we inject CSS via theme.styles (which mounts inside
|
||||
// that shadow root). "Ask AI" stays exclusively on Kapa.
|
||||
theme: {
|
||||
styles: [
|
||||
{
|
||||
key: 'hide-inkeep-ai-chat',
|
||||
type: 'style',
|
||||
value: '.ikp-view_toggle, .ikp-ai-ask-ai-trigger { display: none !important; }',
|
||||
},
|
||||
{
|
||||
key: 'dark-search-overlay',
|
||||
type: 'style',
|
||||
value: '.dark\\:bg-overlay-dark { background-color: rgba(0, 0, 0, 0.75) !important; }',
|
||||
},
|
||||
{
|
||||
key: 'two-row-docs-tabs',
|
||||
type: 'style',
|
||||
value: buildTwoRowTabCss(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
searchSettings: {
|
||||
placeholder: 'Search ClickHouse docs...',
|
||||
// Wait 300ms after the last keystroke before firing a search request,
|
||||
// so fast typing issues one query instead of one per character.
|
||||
debounceTimeMs: 300,
|
||||
// Return at most 20 results per query (down from Inkeep's default 40).
|
||||
maxResults: 20,
|
||||
shouldShowContentSnippets: true,
|
||||
contentSnippetLength: 200,
|
||||
shouldHighlightMatches: true,
|
||||
// Row-1 tabs followed by the docs sub-areas (see SEARCH_TABS). The
|
||||
// two-row-docs-tabs CSS above keeps the sub-areas hidden until 'Docs'
|
||||
// (or one of them) is active. All tabs are populated by transformSource.
|
||||
tabs: SEARCH_TABS,
|
||||
},
|
||||
};
|
||||
|
||||
// cxkit-mintlify auto-binds this to the Mintlify search bar entries
|
||||
// (#search-bar-entry / -mobile / #home-search-entry) and the ⌘K hotkey,
|
||||
// intercepting them (capture-phase preventDefault) so the native search
|
||||
// never opens.
|
||||
Inkeep.ModalSearchAndChat(settings);
|
||||
}
|
||||
|
||||
function boot() {
|
||||
try {
|
||||
injectNoShiftStyle();
|
||||
loadScript(INKEEP_SCRIPT_URL, initInkeep);
|
||||
} catch (e) {
|
||||
console.log('Inkeep: failed to load widget:', e);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
})();
|
||||
@@ -1,83 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Derive a stable per-user identifier from the GA cookie so Kapa can
|
||||
// correlate sessions to a single visitor.
|
||||
function extractGoogleAnalyticsUserIdFromCookie(gaCookie) {
|
||||
if (!gaCookie) return undefined;
|
||||
var parts = gaCookie.split('.').slice(-2);
|
||||
if (parts.length === 2) return parts.join('-');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getBrowserCookie(name) {
|
||||
if (typeof document !== 'object' || typeof document.cookie !== 'string') return undefined;
|
||||
var decoded = decodeURIComponent(document.cookie);
|
||||
var prefix = name + '=';
|
||||
var parts = decoded.split(';');
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var c = parts[i].trim();
|
||||
if (c.indexOf(prefix) === 0) return c.substring(prefix.length);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var gaUserId = extractGoogleAnalyticsUserIdFromCookie(getBrowserCookie('_ga'));
|
||||
window.kapaSettings = { user: { uniqueClientId: gaUserId } };
|
||||
|
||||
// Kapa doesn't support iOS 16.4 or lower — skip injection on those devices.
|
||||
function isOldiOS() {
|
||||
var ua = navigator.userAgent;
|
||||
if (!/iPad|iPhone/.test(ua)) return false;
|
||||
var m = ua.match(/OS (\d+)(?:_(\d+))?/);
|
||||
if (!m) return true;
|
||||
var major = parseInt(m[1], 10);
|
||||
var minor = m[2] ? parseInt(m[2], 10) : 0;
|
||||
if (isNaN(minor)) minor = 0;
|
||||
return major < 16 || (major === 16 && minor <= 4);
|
||||
}
|
||||
|
||||
function insertKapaWidget() {
|
||||
if (document.getElementById('kapa-widget-script')) return;
|
||||
if (isOldiOS()) {
|
||||
console.log('Kapa widget not added: detected iOS 16.4 or lower');
|
||||
return;
|
||||
}
|
||||
|
||||
var script = document.createElement('script');
|
||||
script.id = 'kapa-widget-script';
|
||||
script.src = 'https://widget.kapa.ai/kapa-widget.bundle.js';
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
|
||||
var attrs = {
|
||||
'data-website-id': '5df6cc2b-732e-44e4-b789-aec81b70fe46',
|
||||
'data-project-name': 'ClickHouse',
|
||||
'data-project-color': '#151515',
|
||||
'data-project-logo': 'https://avatars.githubusercontent.com/u/54801242?s=200&v=4',
|
||||
'data-modal-disclaimer': 'This is a custom LLM for ClickHouse with access to all developer documentation, open GitHub Issues, YouTube videos, and resolved StackOverflow posts. Please note that answers are generated by AI and may not be fully accurate, so please use your best judgement.',
|
||||
'data-modal-example-questions': 'How to speed up queries?,How to use materialized views?',
|
||||
// Disable the MCP install dropdown/tab in the Kapa widget header.
|
||||
'data-mcp-enabled': 'false',
|
||||
// Hide Kapa's floating launcher — we open it from the Ask AI button
|
||||
// injected by ask-ai-button.js.
|
||||
'data-launcher-button-hidden': 'true',
|
||||
};
|
||||
|
||||
Object.keys(attrs).forEach(function (k) {
|
||||
script.setAttribute(k, attrs[k]);
|
||||
});
|
||||
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', insertKapaWidget);
|
||||
} else {
|
||||
try {
|
||||
insertKapaWidget();
|
||||
} catch (e) {
|
||||
console.log('An error occurred while trying to load the Kapa.ai widget:', e);
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,49 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Mintlify mounts desktop dropdown menus on demand, so their image assets are
|
||||
// otherwise discovered only when a user first opens a menu. Warm the small
|
||||
// SVGs at low priority as soon as the document is ready so every navbar
|
||||
// dropdown is prepared for its first interaction.
|
||||
var NAV_ICON_PATHS = [
|
||||
'/images/icons/icon-get-started.svg',
|
||||
'/images/icons/icon-concepts.svg',
|
||||
'/images/icons/icon-guides.svg',
|
||||
'/images/icons/icon-reference.svg',
|
||||
'/images/icons/icon-clickhouse-cloud.svg',
|
||||
'/images/icons/icon-postgres.svg',
|
||||
'/images/icons/icon-clickstack.svg',
|
||||
'/images/icons/logo-langfuse.svg',
|
||||
'/images/icons/icon-agentic-data-stack.svg',
|
||||
'/images/icons/icon-chdb.svg',
|
||||
'/images/icons/icon-kubernetes-operator.svg',
|
||||
'/images/icons/icon-clickpipes.svg',
|
||||
'/images/icons/icon-language-clients.svg',
|
||||
'/images/icons/icon-connectors.svg',
|
||||
'/images/icons/icon-support-center.svg',
|
||||
'/images/icons/icon-contribute.svg',
|
||||
'/images/icons/icon-changelogs.svg',
|
||||
'/images/icons/icon-about.svg',
|
||||
];
|
||||
|
||||
var BASE = /^\/docs(\/|$)/.test(window.location.pathname) ? '/docs' : '';
|
||||
|
||||
function warmNavIcons() {
|
||||
// Retain the Image objects for the page session. Besides keeping the SVGs
|
||||
// decoded, this avoids a second validation request in environments such as
|
||||
// `mint dev`, which serves static files with Cache-Control: max-age=0.
|
||||
window.__chNavIconCache = NAV_ICON_PATHS.map(function (path) {
|
||||
var image = new Image();
|
||||
image.fetchPriority = 'low';
|
||||
image.decoding = 'async';
|
||||
image.src = BASE + path;
|
||||
return image;
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', warmNavIcons, { once: true });
|
||||
} else {
|
||||
warmNavIcons();
|
||||
}
|
||||
})();
|
||||
@@ -1,124 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var CTA_ID = 'ch-navbar-cta';
|
||||
var CTA_HREF = 'https://clickhouse.cloud/signUp?loc=docs-nav-signUp-cta';
|
||||
|
||||
var githubSvg = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">'
|
||||
+ '<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1.75C4.27 1.75 1.25 4.77 1.25 8.5c0 2.99 1.93 5.51 4.62 6.4.34.06.46-.14.46-.32 0-.16-.01-.69-.01-1.26-1.7.32-2.14-.6-2.27-.98-.08-.19-.41-.79-.7-.95-.24-.13-.58-.44-.01-.45.53-.01.91.49 1.04.69.61 1.02 1.58.73 1.97.56.06-.44.24-.74.43-.91-1.5-.17-3.07-.75-3.07-3.33 0-.73.26-1.34.69-1.81-.07-.17-.3-.86.07-1.79 0 0 .57-.18 1.86.69.54-.15 1.11-.23 1.69-.23.57 0 1.14.08 1.68.23 1.29-.88 1.86-.69 1.86-.69.37.93.14 1.62.07 1.79.43.47.69 1.07.69 1.81 0 2.59-1.58 3.16-3.08 3.33.25.21.46.62.46 1.25 0 .9-.01 1.63-.01 1.86 0 .18.13.39.47.32 2.67-.9 4.58-3.41 4.58-6.4 0-3.73-3.02-6.75-6.75-6.75Z" fill="currentColor"/>'
|
||||
+ '</svg>';
|
||||
|
||||
function formatStars(count) {
|
||||
if (count >= 1000) return (count / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
||||
return String(count);
|
||||
}
|
||||
|
||||
function injectStyles() {
|
||||
if (document.getElementById('ch-navbar-cta-styles')) return;
|
||||
var style = document.createElement('style');
|
||||
style.id = 'ch-navbar-cta-styles';
|
||||
style.textContent = ''
|
||||
// Hide mobile AI assistant button
|
||||
+ '#assistant-entry-mobile { display: none !important; }'
|
||||
// Invert dark SVG logos so they're visible on dark backgrounds
|
||||
+ '.dark img[src*="windsurf"], :is(.dark) img[src*="windsurf"] { filter: invert(1) !important; }'
|
||||
// CTA container
|
||||
+ '#' + CTA_ID + ' { display: flex; align-items: center; gap: 16px; flex-shrink: 0; margin-left: 32px; }'
|
||||
// GitHub stars link
|
||||
+ '#' + CTA_ID + ' .ch-gh-stars { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 500; text-decoration: none; white-space: nowrap; transition: color 0.15s; }'
|
||||
+ '#' + CTA_ID + ' .ch-gh-stars svg { flex-shrink: 0; }'
|
||||
// Get started button
|
||||
+ '#' + CTA_ID + ' .ch-cta-btn { display: inline-flex; align-items: center; padding: 6px 16px; border-radius: 4px; font-size: 13px; font-weight: 600; text-decoration: none; white-space: nowrap; transition: background-color 0.15s, color 0.15s; }'
|
||||
// Light mode
|
||||
+ '#' + CTA_ID + ' .ch-gh-stars { color: #374151; }'
|
||||
+ '#' + CTA_ID + ' .ch-gh-stars:hover { color: #111; }'
|
||||
+ '#' + CTA_ID + ' .ch-cta-btn { background: #1c1c1c; color: #fff; }'
|
||||
+ '#' + CTA_ID + ' .ch-cta-btn:hover { background: #333; }'
|
||||
// Dark mode
|
||||
+ '.dark #' + CTA_ID + ' .ch-gh-stars { color: #d1d5db; }'
|
||||
+ '.dark #' + CTA_ID + ' .ch-gh-stars:hover { color: #fff; }'
|
||||
+ '.dark #' + CTA_ID + ' .ch-cta-btn { background: #fdff75; color: #1c1c1c; }'
|
||||
+ '.dark #' + CTA_ID + ' .ch-cta-btn:hover { background: #eaec6a; }';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function injectCta() {
|
||||
if (document.getElementById(CTA_ID)) return true;
|
||||
|
||||
var mapleNav = document.getElementById('navbar-transition-maple');
|
||||
if (!mapleNav) return false;
|
||||
|
||||
injectStyles();
|
||||
|
||||
// --- Right section: GitHub stars + Get started ---
|
||||
var container = document.createElement('div');
|
||||
container.id = CTA_ID;
|
||||
|
||||
var ghLink = document.createElement('a');
|
||||
ghLink.className = 'ch-gh-stars';
|
||||
ghLink.href = 'https://github.com/ClickHouse/ClickHouse';
|
||||
ghLink.target = '_blank';
|
||||
ghLink.rel = 'noopener noreferrer';
|
||||
ghLink.innerHTML = githubSvg + '<span class="ch-gh-count"></span>';
|
||||
container.appendChild(ghLink);
|
||||
|
||||
var ctaLink = document.createElement('a');
|
||||
ctaLink.className = 'ch-cta-btn';
|
||||
ctaLink.href = CTA_HREF;
|
||||
ctaLink.target = '_blank';
|
||||
ctaLink.rel = 'noopener noreferrer';
|
||||
ctaLink.textContent = 'Get Started';
|
||||
ctaLink.onclick = function () {
|
||||
if (window.galaxy && typeof window.galaxy.track === 'function') {
|
||||
window.galaxy.track('docs.navbar.get-started', {
|
||||
interaction: 'click',
|
||||
href: CTA_HREF,
|
||||
});
|
||||
}
|
||||
};
|
||||
container.appendChild(ctaLink);
|
||||
|
||||
mapleNav.appendChild(container);
|
||||
|
||||
fetchStarCount(ghLink.querySelector('.ch-gh-count'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function fetchStarCount(el) {
|
||||
if (!el) return;
|
||||
el.textContent = '44.4k';
|
||||
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://api.github.com/repos/ClickHouse/ClickHouse', true);
|
||||
xhr.timeout = 5000;
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
if (data.stargazers_count) {
|
||||
el.textContent = formatStars(data.stargazers_count);
|
||||
}
|
||||
} catch (e) { /* keep fallback */ }
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
} catch (e) { /* keep fallback */ }
|
||||
}
|
||||
|
||||
function init() {
|
||||
injectCta();
|
||||
|
||||
var observer = new MutationObserver(function () {
|
||||
injectCta();
|
||||
});
|
||||
observer.observe(document.documentElement, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -1,67 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Mintlify renders a default "Raise issue" feedback link that opens a blank
|
||||
// issue (issues/new?title=Issue on docs&body=Path: <page>). Point it at the
|
||||
// repository's Documentation issue form instead, carrying the page path
|
||||
// through so the reporter does not have to type it. The path lands in the
|
||||
// form's `page` field (matched by its `id` in
|
||||
// .github/ISSUE_TEMPLATE/60_documentation-issue.yaml).
|
||||
var FORM = 'https://github.com/ClickHouse/ClickHouse/issues/new';
|
||||
var TEMPLATE = '60_documentation-issue.yaml';
|
||||
|
||||
function pagePathFrom(href) {
|
||||
// Prefer the path Mintlify already encoded in the default link's body
|
||||
// ("Path: /foo"); fall back to the current location.
|
||||
try {
|
||||
var body = new URL(href, window.location.origin).searchParams.get('body') || '';
|
||||
var m = body.match(/Path:\s*(\S+)/);
|
||||
if (m) return m[1];
|
||||
} catch (e) {}
|
||||
return window.location.pathname;
|
||||
}
|
||||
|
||||
function targetHref(path) {
|
||||
return FORM + '?template=' + encodeURIComponent(TEMPLATE) +
|
||||
'&page=' + encodeURIComponent(path);
|
||||
}
|
||||
|
||||
function isDefaultRaiseIssue(href) {
|
||||
// Only touch Mintlify's default "Raise issue" link; leave any other
|
||||
// issues/new links (e.g. in page content) alone. Spaces in the title may be
|
||||
// encoded as %20 or +, or left literal in the attribute.
|
||||
return /\/issues\/new\?/.test(href) &&
|
||||
/title=Issue(%20|\+|\s)on(%20|\+|\s)docs/i.test(href);
|
||||
}
|
||||
|
||||
function rewrite() {
|
||||
var links = document.querySelectorAll('a[href*="/issues/new"]');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var href = links[i].getAttribute('href') || '';
|
||||
if (isDefaultRaiseIssue(href)) {
|
||||
links[i].setAttribute('href', targetHref(pagePathFrom(href)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run now and keep watching: Mintlify is a SPA, so the feedback link is
|
||||
// re-rendered (with a fresh default href) on every client-side navigation.
|
||||
// Rewriting is idempotent -- after the rewrite the href no longer matches
|
||||
// isDefaultRaiseIssue -- so re-running on our own attribute change is a no-op
|
||||
// and cannot loop.
|
||||
function start() {
|
||||
rewrite();
|
||||
new MutationObserver(rewrite).observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['href'],
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', start);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
})();
|
||||
@@ -1,62 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// With an announcement banner configured, Next.js skips its scroll-to-top
|
||||
// on client-side navigation: the banner is position:fixed at the top of the
|
||||
// re-rendered segment, so the router considers the new page "already in
|
||||
// viewport" and leaves the scroll position where it was. (Banner-less
|
||||
// Mintlify sites scroll to top as expected; dismissing the banner makes the
|
||||
// bug disappear.) Restore the expected behavior by scrolling to the top
|
||||
// whenever a forward navigation changes the path.
|
||||
//
|
||||
// The path is watched from a rAF loop rather than by wrapping
|
||||
// history.pushState — the router can hold a reference to the original
|
||||
// pushState from before this script runs, which would bypass a wrapper.
|
||||
//
|
||||
// Back/forward (popstate) is deliberately left alone so the browser and
|
||||
// router can restore the previous scroll position. Cross-page hash links
|
||||
// (/page#anchor) scroll to the anchor once the new page has rendered it,
|
||||
// since the banner bug breaks that scroll too.
|
||||
var lastPath = window.location.pathname;
|
||||
var traversed = false;
|
||||
|
||||
window.addEventListener('popstate', function () {
|
||||
if (window.location.pathname !== lastPath) {
|
||||
traversed = true;
|
||||
}
|
||||
});
|
||||
|
||||
// The new page renders some frames after the path changes, so poll for the
|
||||
// anchor target before scrolling to it; if it never appears (bad anchor),
|
||||
// fall back to the top rather than keeping the old page's position.
|
||||
function scrollToAnchor(hash, framesLeft) {
|
||||
var id;
|
||||
try { id = decodeURIComponent(hash.slice(1)); } catch (e) { id = hash.slice(1); }
|
||||
var el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.scrollIntoView();
|
||||
return;
|
||||
}
|
||||
if (framesLeft > 0 && window.location.hash === hash) {
|
||||
window.requestAnimationFrame(function () { scrollToAnchor(hash, framesLeft - 1); });
|
||||
} else {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function watch() {
|
||||
var path = window.location.pathname;
|
||||
if (path !== lastPath) {
|
||||
lastPath = path;
|
||||
if (traversed) {
|
||||
traversed = false;
|
||||
} else if (window.location.hash) {
|
||||
scrollToAnchor(window.location.hash, 180);
|
||||
} else {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
window.requestAnimationFrame(watch);
|
||||
}
|
||||
window.requestAnimationFrame(watch);
|
||||
})();
|
||||
@@ -1,272 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// ── Desktop tab navigation ────────────────────────────────────────────────
|
||||
var TAB_URLS = {
|
||||
'Home': '/',
|
||||
'Database': '/get-started/about/intro',
|
||||
'Solutions': '/products/cloud/getting-started/cloud-get-started',
|
||||
'Integrations': '/integrations/home',
|
||||
};
|
||||
|
||||
// Locales with their own page tree; each has a localized homepage at
|
||||
// /<locale> and mirrors the English paths beneath it.
|
||||
var LOCALES = ['ar', 'es', 'fr', 'ja', 'ko', 'pt-BR', 'ru', 'zh'];
|
||||
|
||||
// '' at root (.app, mint dev); '/docs' on the subpath deploy
|
||||
var BASE = /^\/docs(\/|$)/.test(window.location.pathname) ? '/docs' : '';
|
||||
function stripBase(p) { return (BASE && p.indexOf(BASE) === 0) ? (p.slice(BASE.length) || '/') : p; }
|
||||
|
||||
function currentLocale() {
|
||||
var seg = stripBase(window.location.pathname).split('/')[1] || '';
|
||||
return LOCALES.indexOf(seg) !== -1 ? seg : '';
|
||||
}
|
||||
|
||||
// Keep navigation within the active locale and base path.
|
||||
function localizeUrl(url) {
|
||||
var locale = currentLocale();
|
||||
var localized = locale ? '/' + locale + (url === '/' ? '' : url) : url;
|
||||
return BASE + localized;
|
||||
}
|
||||
|
||||
function patchTabButtons() {
|
||||
// Only run on desktop
|
||||
if (window.innerWidth < 1024) return;
|
||||
|
||||
document.querySelectorAll('button.nav-tabs-item, a.nav-tabs-item').forEach(function (el) {
|
||||
if (el.dataset.tabNavAttached) return;
|
||||
|
||||
var labelDiv = el.querySelector('div');
|
||||
if (!labelDiv) return;
|
||||
|
||||
var text = (labelDiv.textContent || '').trim();
|
||||
var url = TAB_URLS[text];
|
||||
if (!url) return;
|
||||
|
||||
el.dataset.tabNavAttached = '1';
|
||||
|
||||
if (el.tagName === 'A') {
|
||||
el.setAttribute('href', localizeUrl(url));
|
||||
return;
|
||||
}
|
||||
|
||||
labelDiv.style.cursor = 'pointer';
|
||||
labelDiv.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
window.location.href = localizeUrl(url);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ── Mobile section header styling ─────────────────────────────────────────
|
||||
var SECTION_HEADERS = ['ClickHouse', 'Open source'];
|
||||
|
||||
function styleDropdownHeaders() {
|
||||
document.querySelectorAll('a.mobile-nav-tabs-item').forEach(function (a) {
|
||||
if (a.dataset.headerStyled) return;
|
||||
var text = (a.textContent || '').trim();
|
||||
if (SECTION_HEADERS.indexOf(text) === -1) return;
|
||||
|
||||
a.dataset.headerStyled = '1';
|
||||
a.style.fontWeight = '700';
|
||||
a.style.fontSize = '0.75rem';
|
||||
a.style.letterSpacing = '0.05em';
|
||||
a.style.textTransform = 'uppercase';
|
||||
a.style.opacity = '0.5';
|
||||
a.style.pointerEvents = 'none';
|
||||
a.style.cursor = 'default';
|
||||
a.style.borderBottom = '1px solid rgba(255,255,255,0.08)';
|
||||
a.style.paddingBottom = '8px';
|
||||
a.style.marginBottom = '2px';
|
||||
|
||||
a.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ── Logo theme sync ──────────────────────────────────────────────────────
|
||||
function updateLogoTheme() {
|
||||
var light = document.getElementById('ch-hp-logo-light');
|
||||
var dark = document.getElementById('ch-hp-logo-dark');
|
||||
if (!light || !dark) return;
|
||||
var isDark = document.documentElement.classList.contains('dark');
|
||||
light.style.display = isDark ? 'none' : 'block';
|
||||
dark.style.display = isDark ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// ── Homepage sidebar hiding ───────────────────────────────────────────────
|
||||
// Each locale has its own homepage at /<locale> (e.g. /es, /ja); treat those
|
||||
// the same as the English homepage at /.
|
||||
function isHomePath() {
|
||||
var path = stripBase(window.location.pathname).replace(/\/+$/, '') || '/';
|
||||
return path === '/' || LOCALES.indexOf(path.slice(1)) !== -1;
|
||||
}
|
||||
|
||||
function applyHomepageClass() {
|
||||
if (isHomePath()) {
|
||||
document.documentElement.classList.add('ch-homepage');
|
||||
} else {
|
||||
document.documentElement.classList.remove('ch-homepage');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Homepage: inject logo + theme toggle into navbar ──────────────────────
|
||||
var LOGO_ID = 'ch-homepage-logo';
|
||||
var TOGGLE_ID = 'ch-homepage-toggle';
|
||||
|
||||
function findSidebarThemeToggle() {
|
||||
// Current Mintlify builds render a segmented control: a
|
||||
// <div role="group" aria-label="Theme preference">
|
||||
// wrapping three buttons (system / light / dark). We relocate the whole
|
||||
// group so all three options move together.
|
||||
var group = document.querySelector('#sidebar [role="group"][aria-label="Theme preference"]');
|
||||
if (group) return group;
|
||||
// Older builds rendered a single <button aria-label="Toggle dark mode">.
|
||||
var btn = document.querySelector('#sidebar button[aria-label="Toggle dark mode"]');
|
||||
if (btn) return btn;
|
||||
// Localized UIs translate the aria-label (e.g. "다크 모드 전환"), so fall
|
||||
// back to the control's shape: the only sidebar pill (rounded-full) holding
|
||||
// the theme icons — matches both the segmented group and the legacy button.
|
||||
var candidates = document.querySelectorAll('#sidebar [role="group"], #sidebar button');
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
if (/rounded-full/.test(candidates[i].className)
|
||||
&& candidates[i].querySelectorAll('svg').length >= 2) {
|
||||
return candidates[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setupHomepageNavbar() {
|
||||
var isHome = isHomePath();
|
||||
var navbar = document.getElementById('navbar-transition-maple');
|
||||
if (!navbar) return;
|
||||
|
||||
if (!isHome) {
|
||||
// Restore theme toggle to sidebar before removing wrapper
|
||||
var toggleWrapper = document.getElementById(TOGGLE_ID);
|
||||
if (toggleWrapper) {
|
||||
// Move the whole control back — it may be a segmented group (a div with
|
||||
// several buttons), not a single button, so restore firstElementChild.
|
||||
var control = toggleWrapper.firstElementChild;
|
||||
var sidebar = document.getElementById('sidebar');
|
||||
if (control && sidebar) sidebar.appendChild(control);
|
||||
toggleWrapper.parentNode.removeChild(toggleWrapper);
|
||||
}
|
||||
var logo = document.getElementById(LOGO_ID);
|
||||
if (logo) logo.parentNode.removeChild(logo);
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject logo at left of navbar; link to the active locale's homepage
|
||||
var existingLogo = document.getElementById(LOGO_ID);
|
||||
if (existingLogo) {
|
||||
existingLogo.setAttribute('href', localizeUrl('/'));
|
||||
} else {
|
||||
var logoLink = document.createElement('a');
|
||||
logoLink.id = LOGO_ID;
|
||||
logoLink.href = localizeUrl('/');
|
||||
logoLink.innerHTML = '<img src="' + BASE + '/_site/logo/light.svg" id="ch-hp-logo-light" alt="ClickHouse Docs">'
|
||||
+ '<img src="' + BASE + '/_site/logo/dark.svg" id="ch-hp-logo-dark" alt="ClickHouse Docs">';
|
||||
navbar.insertBefore(logoLink, navbar.firstChild);
|
||||
updateLogoTheme();
|
||||
}
|
||||
|
||||
// Move theme toggle from sidebar to navbar — insert after logo, before tabs.
|
||||
// margin-right: auto pushes all tabs + right-side controls to the right.
|
||||
if (!document.getElementById(TOGGLE_ID)) {
|
||||
var sidebarToggle = findSidebarThemeToggle();
|
||||
if (sidebarToggle) {
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.id = TOGGLE_ID;
|
||||
wrapper.style.cssText = 'display:flex;align-items:center;flex-shrink:0;margin-left:0.75rem;margin-right:auto;';
|
||||
sidebarToggle.parentNode.removeChild(sidebarToggle);
|
||||
wrapper.appendChild(sidebarToggle);
|
||||
// Insert as second child (right after the logo)
|
||||
var logoEl = document.getElementById(LOGO_ID);
|
||||
if (logoEl && logoEl.nextSibling) {
|
||||
navbar.insertBefore(wrapper, logoEl.nextSibling);
|
||||
} else {
|
||||
navbar.appendChild(wrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Navbar ready ──────────────────────────────────────────────────────────
|
||||
// Reveal the navbar once both injections are complete: logo (homepage only)
|
||||
// and CTA (all pages). Called via requestAnimationFrame so it runs after the
|
||||
// current synchronous task — giving navbar-cta.js time to inject the CTA
|
||||
// before we check. The MutationObserver re-fires on every DOM change
|
||||
// (including when navbar-cta.js appends the CTA), so if the first RAF check
|
||||
// fails because the CTA isn't there yet, a subsequent observer tick will retry.
|
||||
function markNavbarReady() {
|
||||
var navbar = document.getElementById('navbar-transition-maple');
|
||||
if (!navbar) return;
|
||||
if (!document.getElementById('ch-navbar-cta')) return;
|
||||
if (isHomePath() && !document.getElementById(LOGO_ID)) return;
|
||||
navbar.classList.add('ch-navbar-ready');
|
||||
// Latch the reveal on <html> (which survives SPA navigations) so the
|
||||
// first-load hide guard in CSS never re-applies on subsequent in-app
|
||||
// navigations — preventing a fade on every page change.
|
||||
document.documentElement.classList.add('ch-navbar-revealed');
|
||||
}
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────
|
||||
function init() {
|
||||
applyHomepageClass();
|
||||
setupHomepageNavbar();
|
||||
patchTabButtons();
|
||||
styleDropdownHeaders();
|
||||
markNavbarReady();
|
||||
|
||||
// Debounce the observer so rapid React re-renders don't thrash the main
|
||||
// thread with repeated querySelectorAll calls — each flush still runs all
|
||||
// work, but at most once per animation frame rather than once per DOM node.
|
||||
var rafId = null;
|
||||
var observer = new MutationObserver(function () {
|
||||
if (rafId) return;
|
||||
rafId = requestAnimationFrame(function () {
|
||||
rafId = null;
|
||||
applyHomepageClass();
|
||||
setupHomepageNavbar();
|
||||
updateLogoTheme();
|
||||
patchTabButtons();
|
||||
styleDropdownHeaders();
|
||||
markNavbarReady();
|
||||
});
|
||||
});
|
||||
observer.observe(document.documentElement, { childList: true, subtree: true });
|
||||
|
||||
window.addEventListener('resize', patchTabButtons);
|
||||
window.addEventListener('popstate', function () {
|
||||
applyHomepageClass();
|
||||
setupHomepageNavbar();
|
||||
});
|
||||
|
||||
// Failsafe: never leave the navbar hidden. If an injection never completes
|
||||
// (so markNavbarReady never latches the reveal), force-reveal after a
|
||||
// generous delay — but only once the navbar actually exists, so a late
|
||||
// mount still gets the initial opacity hide instead of flashing in.
|
||||
setTimeout(function retryNavbarReveal() {
|
||||
if (!document.getElementById('navbar-transition-maple')) {
|
||||
setTimeout(retryNavbarReveal, 100);
|
||||
return;
|
||||
}
|
||||
document.documentElement.classList.add('ch-navbar-revealed');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Apply the homepage class immediately at script evaluation time — before the
|
||||
// navbar is inserted by React hydration — so justify-content:flex-start and
|
||||
// other ch-homepage navbar rules apply from the very first navbar paint.
|
||||
applyHomepageClass();
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -1,26 +0,0 @@
|
||||
<svg width="260" height="260" viewBox="0 0 260 260" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.background {
|
||||
fill: #1E1E1E;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.background {
|
||||
fill: transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect class='background' width="260" height="260" rx="40" fill="none"/>
|
||||
<g clip-path="url(#clip0_39_1086)">
|
||||
<rect x="42.25" y="42.25" width="19.4993" height="175.494" rx="2.05109" fill="white"/>
|
||||
<rect x="81.251" y="42.25" width="19.4993" height="175.494" rx="2.05109" fill="white"/>
|
||||
<rect x="120.252" y="42.25" width="19.4993" height="175.494" rx="2.05109" fill="white"/>
|
||||
<rect x="159.243" y="42.25" width="19.4993" height="175.494" rx="2.05109" fill="white"/>
|
||||
<rect x="198.254" y="110.501" width="19.4993" height="38.9987" rx="2.05109" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_39_1086">
|
||||
<rect width="208" height="208" fill="white" transform="translate(26 26)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 964 B |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 11 KiB |
File diff suppressed because it is too large
Load Diff
@@ -1,171 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate the knowledge-base search index consumed by the KB explorer.
|
||||
|
||||
This script scans resources/support-center/knowledge-base/ for article .mdx
|
||||
files, extracts metadata from their frontmatter, and writes the index to a
|
||||
generated JS module:
|
||||
|
||||
snippets/components/KBExplorer/kb-data.jsx -> export const kbIndex = {...}
|
||||
|
||||
resources/support-center/home.mdx imports that module and passes it to
|
||||
<KBExplorer index={kbIndex} />. We embed the index via a static import rather
|
||||
than fetch it at runtime because mint dev does not serve raw .json files. The
|
||||
module must be .jsx (not .json or .js): Mintlify only bundles .jsx snippet
|
||||
imports as ES modules — a .js import is loaded as a classic script and throws
|
||||
"Unexpected token 'export'", and JSON can't be statically imported here.
|
||||
|
||||
Usage:
|
||||
python _site/scripts/update_kb.py
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Tuple
|
||||
|
||||
# Directory slug -> human label, matching the sidebar groups in
|
||||
# resources/support-center/navigation.json. Unlisted dirs fall back to a
|
||||
# title-cased version of the slug.
|
||||
CATEGORY_LABELS = {
|
||||
'performance-optimization': 'Performance & optimization',
|
||||
'cloud-services': 'Cloud',
|
||||
'data-import-export': 'Data import & export',
|
||||
'configuration-settings': 'Configuration & settings',
|
||||
'integrations': 'Integrations & client libraries',
|
||||
'troubleshooting': 'Troubleshooting & errors',
|
||||
'tables-schema': 'Tables & schema',
|
||||
'queries-sql': 'Queries & SQL',
|
||||
'materialized-views': 'Materialized views & projections',
|
||||
'monitoring-debugging': 'Monitoring & debugging',
|
||||
'security': 'Security & access control',
|
||||
'data-management': 'Data management',
|
||||
'setup-installation': 'Setup & installation',
|
||||
'general-faqs': 'General & FAQs',
|
||||
}
|
||||
|
||||
def parse_frontmatter(content: str) -> Dict[str, Any]:
|
||||
"""Parse simple YAML frontmatter (key: value and inline [arrays])."""
|
||||
match = re.match(r'^---\s*\n(.*?)\n---\s*\n', content, re.DOTALL)
|
||||
if not match:
|
||||
return {}
|
||||
|
||||
frontmatter: Dict[str, Any] = {}
|
||||
for line in match.group(1).split('\n'):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#') or ':' not in line:
|
||||
continue
|
||||
|
||||
key, value = line.split(':', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
|
||||
if value.startswith('[') and value.endswith(']'):
|
||||
items = [item.strip().strip('"').strip("'")
|
||||
for item in value[1:-1].split(',')]
|
||||
frontmatter[key] = [item for item in items if item]
|
||||
else:
|
||||
frontmatter[key] = value.strip('"').strip("'")
|
||||
|
||||
return frontmatter
|
||||
|
||||
|
||||
def is_article(frontmatter: Dict[str, Any], file_path: Path) -> bool:
|
||||
"""Exclude landing/index pages — they aren't individual articles."""
|
||||
if frontmatter.get('doc_type') == 'landing-page':
|
||||
return False
|
||||
if file_path.stem == 'index' or file_path.stem.endswith('-index'):
|
||||
return False
|
||||
return bool(frontmatter.get('title'))
|
||||
|
||||
|
||||
def extract_article(file_path: Path, kb_dir: Path) -> Dict[str, Any]:
|
||||
"""Extract a single article's index entry from its MDX file."""
|
||||
content = file_path.read_text(encoding='utf-8')
|
||||
frontmatter = parse_frontmatter(content)
|
||||
|
||||
rel_path = file_path.relative_to(kb_dir)
|
||||
category_slug = rel_path.parts[0]
|
||||
article_id = str(rel_path.with_suffix('')).replace('\\', '/')
|
||||
|
||||
tags = frontmatter.get('tags', [])
|
||||
if isinstance(tags, str):
|
||||
tags = [tags] if tags else []
|
||||
|
||||
return {
|
||||
'id': article_id,
|
||||
'title': frontmatter.get('title', ''),
|
||||
'description': frontmatter.get('description', ''),
|
||||
'href': '/resources/support-center/knowledge-base/' + article_id,
|
||||
'category': CATEGORY_LABELS.get(
|
||||
category_slug,
|
||||
category_slug.replace('-', ' ').title()
|
||||
),
|
||||
'tags': tags,
|
||||
}
|
||||
|
||||
|
||||
def build_index(kb_dir: Path) -> Tuple[Dict[str, Any], int]:
|
||||
files = []
|
||||
for file_path in kb_dir.glob('**/*.mdx'):
|
||||
# Skip files in underscore-prefixed directories (assets/partials).
|
||||
rel_parts = file_path.relative_to(kb_dir).parts[:-1]
|
||||
if any(part.startswith('_') for part in rel_parts):
|
||||
continue
|
||||
files.append(file_path)
|
||||
|
||||
articles = []
|
||||
skipped = 0
|
||||
for file_path in sorted(set(files)):
|
||||
frontmatter = parse_frontmatter(file_path.read_text(encoding='utf-8'))
|
||||
if not is_article(frontmatter, file_path):
|
||||
skipped += 1
|
||||
continue
|
||||
articles.append(extract_article(file_path, kb_dir))
|
||||
|
||||
articles.sort(key=lambda a: a['title'].lower())
|
||||
|
||||
return {
|
||||
'categories': sorted({a['category'] for a in articles}),
|
||||
'tags': sorted({tag for a in articles for tag in a['tags']}),
|
||||
'articles': articles,
|
||||
}, skipped
|
||||
|
||||
|
||||
def render_module(index: Dict[str, Any]) -> str:
|
||||
"""Render the kb-data.jsx module body.
|
||||
|
||||
The index is emitted as JSON, which is valid JS object-literal syntax, so
|
||||
titles/descriptions with apostrophes, backticks, smart quotes, or non-Latin
|
||||
text need no special escaping. ensure_ascii=False keeps non-ASCII readable.
|
||||
"""
|
||||
body = json.dumps(index, indent=2, ensure_ascii=False)
|
||||
return (
|
||||
"// AUTO-GENERATED by _site/scripts/update_kb.py — do not edit by hand.\n"
|
||||
"// Re-run the script to refresh the knowledge-base index.\n"
|
||||
f"export const kbIndex = {body};\n"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
project_root = Path(__file__).resolve().parents[2]
|
||||
kb_dir = project_root / 'resources' / 'support-center' / 'knowledge-base'
|
||||
output_path = (project_root / 'snippets' / 'components' / 'KBExplorer'
|
||||
/ 'kb-data.jsx')
|
||||
|
||||
if not kb_dir.exists():
|
||||
print(f"Error: knowledge-base directory not found: {kb_dir}")
|
||||
return 1
|
||||
|
||||
index, skipped = build_index(kb_dir)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(render_module(index), encoding='utf-8')
|
||||
|
||||
print(f"✓ Wrote {len(index['articles'])} articles to {output_path}")
|
||||
print(f" {len(index['categories'])} categories, {len(index['tags'])} tags "
|
||||
f"({skipped} non-article pages skipped)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
@@ -1,402 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to automatically extract quick-start metadata and regenerate the
|
||||
quickstarts-data.jsx modules
|
||||
|
||||
This script scans the docs/get-started/quickstarts/ directory for .mdx files,
|
||||
extracts metadata from their frontmatter, and writes it to
|
||||
snippets/components/QuickStartsGrid/quickstarts-data.jsx, which home.mdx
|
||||
imports. It does the same for every locale tree (docs/<locale>/get-started/
|
||||
quickstarts/ -> snippets/<locale>/components/QuickStartsGrid/
|
||||
quickstarts-data.jsx), so the cards pick up the translated titles and
|
||||
descriptions from the locale pages' frontmatter. The data lives in a snippets
|
||||
module rather than inline in home.mdx because the translation pipeline
|
||||
translates snippet modules but not `export const` literals inside pages
|
||||
(same layout as KBExplorer's kb-data.jsx).
|
||||
|
||||
Usage:
|
||||
python scripts/update_quickstarts.py
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
|
||||
def unquote_scalar(value: str) -> str:
|
||||
"""Unquote a single YAML scalar, honoring the escaping the docs frontmatter
|
||||
actually uses.
|
||||
|
||||
Inside a single-quoted scalar a doubled '' is the one literal apostrophe
|
||||
YAML allows ('l''immobilier' -> l'immobilier); inside a double-quoted scalar
|
||||
a backslash escapes the next character. The previous code stripped the outer
|
||||
quotes and emitted the rest verbatim, so escaped apostrophes leaked into the
|
||||
generated card data. A real YAML parser would be ideal, but PyYAML is not
|
||||
available in the docs CI image, so unescape the two quoting styles by hand.
|
||||
"""
|
||||
value = value.strip()
|
||||
if len(value) >= 2 and value[0] == "'" and value[-1] == "'":
|
||||
return value[1:-1].replace("''", "'")
|
||||
if len(value) >= 2 and value[0] == '"' and value[-1] == '"':
|
||||
# Unescape \" and \\ left-to-right (re.sub matches non-overlapping).
|
||||
return re.sub(r'\\(["\\])', r'\1', value[1:-1])
|
||||
return value
|
||||
|
||||
def parse_frontmatter(content: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Parse YAML frontmatter from MDX file content.
|
||||
|
||||
Args:
|
||||
content: The full content of the MDX file
|
||||
|
||||
Returns:
|
||||
Dictionary containing the frontmatter fields
|
||||
"""
|
||||
# Match frontmatter between --- delimiters
|
||||
match = re.match(r'^---\s*\n(.*?)\n---\s*\n', content, re.DOTALL)
|
||||
if not match:
|
||||
return {}
|
||||
|
||||
frontmatter_text = match.group(1)
|
||||
frontmatter = {}
|
||||
|
||||
# Parse simple YAML key-value pairs and arrays
|
||||
for line in frontmatter_text.split('\n'):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
|
||||
# Handle key: value pairs
|
||||
if ':' in line:
|
||||
key, value = line.split(':', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
|
||||
# Handle arrays like [item1, item2]. Brackets are unquoted, so this
|
||||
# is checked before unquoting the scalar form below.
|
||||
if value.startswith('[') and value.endswith(']'):
|
||||
# Parse array
|
||||
array_content = value[1:-1]
|
||||
items = [unquote_scalar(item)
|
||||
for item in array_content.split(',')]
|
||||
frontmatter[key] = [item for item in items if item]
|
||||
else:
|
||||
frontmatter[key] = unquote_scalar(value)
|
||||
|
||||
return frontmatter
|
||||
|
||||
def slugify_tag(value: str) -> str:
|
||||
"""
|
||||
Normalize a frontmatter tag value to the stable slug the QuickStartsGrid
|
||||
filter options match against ('AI/ML' -> 'ai-ml'). Slugs survive the
|
||||
translation pipeline (which localizes display labels but leaves
|
||||
identifier-like strings alone), so filtering keeps working on locale pages.
|
||||
"""
|
||||
slug = re.sub(r'[^a-z0-9]+', '-', value.lower()).strip('-')
|
||||
# 'OSS' means self-managed; the grid exposes only the latter as an option.
|
||||
return {'oss': 'self-managed'}.get(slug, slug)
|
||||
|
||||
def extract_quickstart_data(file_path: Path, base_dir: Path) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract quick-start metadata from an MDX file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the MDX file
|
||||
base_dir: Base directory for generating relative paths
|
||||
|
||||
Returns:
|
||||
Dictionary containing the quick-start data
|
||||
"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
frontmatter = parse_frontmatter(content)
|
||||
|
||||
# Generate ID from filename (remove .mdx extension)
|
||||
file_id = file_path.stem
|
||||
|
||||
# Generate href from the path relative to the project root
|
||||
rel_path = file_path.relative_to(base_dir)
|
||||
href = '/' + str(rel_path.parent / file_path.stem).replace('\\', '/')
|
||||
|
||||
# Extract fields with defaults
|
||||
quickstart = {
|
||||
'id': file_id,
|
||||
'title': frontmatter.get('title', file_id.replace('-', ' ').title()),
|
||||
'description': frontmatter.get('description', ''),
|
||||
'href': href,
|
||||
'useCases': frontmatter.get('useCases', []),
|
||||
'products': frontmatter.get('products', []),
|
||||
}
|
||||
|
||||
# Add icon if present
|
||||
if 'icon' in frontmatter:
|
||||
quickstart['icon'] = frontmatter['icon']
|
||||
|
||||
return quickstart
|
||||
|
||||
def find_quickstart_files(quickstarts_dir: Path) -> List[Path]:
|
||||
"""
|
||||
Find all quick-start MD/MDX files (excluding home.mdx).
|
||||
|
||||
Args:
|
||||
quickstarts_dir: Path to the quickstarts directory
|
||||
|
||||
Returns:
|
||||
List of Path objects for quick-start files
|
||||
"""
|
||||
files = []
|
||||
|
||||
# Find both .md and .mdx files
|
||||
for pattern in ['**/*.mdx', '**/*.md']:
|
||||
for file_path in quickstarts_dir.glob(pattern):
|
||||
# Skip home.mdx, the README, and anything underscore-prefixed
|
||||
# (templates like _TEMPLATE.mdx and helper directories)
|
||||
if file_path.name in ('home.mdx', 'README.md'):
|
||||
continue
|
||||
if any(part.startswith('_') for part in file_path.relative_to(quickstarts_dir).parts):
|
||||
continue
|
||||
files.append(file_path)
|
||||
|
||||
# Remove duplicates and sort
|
||||
files = sorted(set(files))
|
||||
|
||||
return files
|
||||
|
||||
def generate_badges(use_cases: List[str], products: List[str]) -> str:
|
||||
"""
|
||||
Generate Badge components for use cases and products.
|
||||
|
||||
Args:
|
||||
use_cases: List of use case tags
|
||||
products: List of product tags
|
||||
|
||||
Returns:
|
||||
Badge components as a string
|
||||
"""
|
||||
# First line: muted text back-link to the quickstarts home. Its markup and
|
||||
# styling live in a single shared partial,
|
||||
# get-started/quickstarts/_back-to-quickstarts.mdx, which every quickstart
|
||||
# page imports as <BackToQuickstarts /> (Mintlify resolves the import to the
|
||||
# localized copy per locale). Keeping the link in one partial avoids
|
||||
# duplicating the styling across every page and locale; the page's import of
|
||||
# the partial is what makes this tag resolve.
|
||||
first_line = '<BackToQuickstarts />'
|
||||
|
||||
# Second line: all other badges
|
||||
second_line_badges = []
|
||||
|
||||
# Expand 'All' to all use cases for badge display
|
||||
all_use_cases = ['Real-Time Analytics', 'Data Warehousing', 'Observability', 'AI/ML']
|
||||
display_use_cases = all_use_cases if 'All' in use_cases else use_cases
|
||||
|
||||
# Add use case badges (blue)
|
||||
for use_case in display_use_cases:
|
||||
# Capitalize properly
|
||||
display_text = use_case.title() if use_case.lower() != 'ai/ml' else 'AI/ML'
|
||||
second_line_badges.append(f'<Badge size="lg" color="blue">{display_text}</Badge>')
|
||||
|
||||
# Add product badges (orange). Brand/acronym names must not be title-cased
|
||||
# ('OSS'.title() == 'Oss').
|
||||
product_labels = {'oss': 'OSS', 'chdb': 'chDB', 'clickpipes': 'ClickPipes', 'clickstack': 'ClickStack'}
|
||||
for product in products:
|
||||
display_text = product_labels.get(product.lower(), product.title())
|
||||
second_line_badges.append(f'<Badge size="lg" color="orange">{display_text}</Badge>')
|
||||
|
||||
# Combine with line break and add margin
|
||||
second_line = '\n'.join(second_line_badges)
|
||||
|
||||
return f'{first_line}\n<div className="mt-2 flex flex-wrap gap-2">\n{second_line}\n</div>'
|
||||
|
||||
def update_quickstart_badges(file_path: Path, use_cases: List[str], products: List[str]) -> Optional[str]:
|
||||
"""
|
||||
Refresh the autogenerated badges section of a quick-start MDX file.
|
||||
|
||||
Returns the updated page content, or None when the badge block is already
|
||||
up to date. Nothing is written here: the caller stages the content and
|
||||
flushes it only after the whole scan has succeeded, so a failure on a
|
||||
later page cannot leave the tree half-regenerated.
|
||||
|
||||
Args:
|
||||
file_path: Path to the quick-start MDX file
|
||||
use_cases: List of use case tags
|
||||
products: List of product tags
|
||||
"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Generate badges
|
||||
badges = generate_badges(use_cases, products)
|
||||
|
||||
# Pattern to match the autogenerated section
|
||||
pattern = r'\{/\* AUTOGENERATED_START \*/\}.*?\{/\* AUTOGENERATED_END \*/\}'
|
||||
|
||||
# Without the markers the badge block cannot be updated and the page would
|
||||
# silently keep stale badges, so this is an error, not a warning.
|
||||
if not re.search(pattern, content, re.DOTALL):
|
||||
raise ValueError(
|
||||
"no {/* AUTOGENERATED_START */}/{/* AUTOGENERATED_END */} markers "
|
||||
"found; cannot update the badge block (copy the marker pair from "
|
||||
"_TEMPLATE.mdx)"
|
||||
)
|
||||
|
||||
# Replace the content between markers
|
||||
replacement = f'{{/* AUTOGENERATED_START */}}\n{badges}\n{{/* AUTOGENERATED_END */}}'
|
||||
updated = re.sub(pattern, replacement, content, flags=re.DOTALL)
|
||||
return updated if updated != content else None
|
||||
|
||||
def render_data_module(quickstarts: List[Dict[str, Any]]) -> str:
|
||||
"""Render the quickstarts-data.jsx module body.
|
||||
|
||||
Tag values are emitted as slugs (the form the grid's filter options match
|
||||
against); the in-page badges keep the raw frontmatter values.
|
||||
|
||||
The data is emitted as JSON, which is valid JS object-literal syntax, so
|
||||
titles/descriptions with apostrophes, backticks, smart quotes, or non-Latin
|
||||
text need no special escaping. ensure_ascii=False keeps non-ASCII readable.
|
||||
"""
|
||||
slugged = [
|
||||
{**qs,
|
||||
'useCases': [slugify_tag(v) for v in qs.get('useCases', [])],
|
||||
'products': [slugify_tag(v) for v in qs.get('products', [])]}
|
||||
for qs in quickstarts
|
||||
]
|
||||
body = json.dumps(slugged, indent=2, ensure_ascii=False)
|
||||
return (
|
||||
"// AUTO-GENERATED by _site/scripts/update_quickstarts.py — do not edit by hand.\n"
|
||||
"// Re-run the script to refresh the quick-start card data.\n"
|
||||
f"export const quickStartsData = {body};\n"
|
||||
)
|
||||
|
||||
def build_quickstarts(quickstarts_dir: Path, project_root: Path,
|
||||
update_badges: bool,
|
||||
staged: Dict[Path, str]) -> Tuple[List[Dict[str, Any]], int]:
|
||||
"""Extract quick-start data from every page in quickstarts_dir.
|
||||
|
||||
Returns the extracted data and the number of pages that failed. Failed
|
||||
pages are reported but do not stop the scan, so one run surfaces every
|
||||
problem; the caller must treat a non-zero failure count as an error
|
||||
rather than shipping data that silently omits the failed pages.
|
||||
|
||||
Refreshed badge blocks are added to `staged` (path -> new content), not
|
||||
written: the caller flushes them only after every scan has succeeded.
|
||||
"""
|
||||
files = find_quickstart_files(quickstarts_dir)
|
||||
quickstarts = []
|
||||
failures = 0
|
||||
seen_ids = {}
|
||||
for file_path in files:
|
||||
try:
|
||||
data = extract_quickstart_data(file_path, project_root)
|
||||
|
||||
# Ids are the filename stem, so the recursive scan lets two pages in
|
||||
# different subdirectories collide (foo/setup.mdx and bar/setup.mdx
|
||||
# both -> 'setup'). A collision would silently drop one page from the
|
||||
# id->page map used for cross-locale tag inheritance, and emit
|
||||
# duplicate ids for React keys and featured lookup, so fail closed
|
||||
# (the non-zero failure count below blocks every write).
|
||||
prior = seen_ids.get(data['id'])
|
||||
if prior is not None:
|
||||
raise ValueError(
|
||||
f"duplicate quickstart id {data['id']!r}; also produced by "
|
||||
f"{prior.relative_to(quickstarts_dir)}. Ids are the filename "
|
||||
"stem and must be unique across the tree — rename one file."
|
||||
)
|
||||
seen_ids[data['id']] = file_path
|
||||
|
||||
if update_badges:
|
||||
updated = update_quickstart_badges(file_path, data['useCases'], data['products'])
|
||||
if updated is not None:
|
||||
staged[file_path] = updated
|
||||
|
||||
quickstarts.append(data)
|
||||
print(f" ✓ {file_path.name}: {data['title']}")
|
||||
except Exception as e:
|
||||
print(f" ✗ {file_path.name}: Error - {e}")
|
||||
failures += 1
|
||||
return quickstarts, failures
|
||||
|
||||
def main():
|
||||
"""Main function to run the script."""
|
||||
# Get the project root directory (this script lives in _site/scripts/)
|
||||
project_root = Path(__file__).resolve().parents[2]
|
||||
|
||||
quickstarts_dir = project_root / 'get-started' / 'quickstarts'
|
||||
if not quickstarts_dir.exists():
|
||||
print(f"Error: Quick-starts directory not found: {quickstarts_dir}")
|
||||
return 1
|
||||
|
||||
# Every write (badge blocks and data modules) is staged here and flushed
|
||||
# only after all trees have scanned cleanly, so a failure anywhere leaves
|
||||
# the tree untouched instead of half-regenerated.
|
||||
staged = {}
|
||||
|
||||
# English tree: extract data and refresh the in-page badge blocks.
|
||||
print(f"Scanning for quick-start files in {quickstarts_dir}...")
|
||||
quickstarts, failures = build_quickstarts(quickstarts_dir, project_root,
|
||||
update_badges=True, staged=staged)
|
||||
if not quickstarts:
|
||||
print("No valid quick-start data extracted")
|
||||
return 1
|
||||
|
||||
output_path = (project_root / 'snippets' / 'components' / 'QuickStartsGrid'
|
||||
/ 'quickstarts-data.jsx')
|
||||
staged[output_path] = render_data_module(quickstarts)
|
||||
print(f"✓ Staged {len(quickstarts)} quick-start(s) for {output_path}")
|
||||
|
||||
# Locale trees: same extraction against the translated pages, so titles and
|
||||
# descriptions come out localized and hrefs come out locale-prefixed
|
||||
# (extract_quickstart_data derives the href from the path relative to the
|
||||
# project root). Badges are left to the translation pipeline.
|
||||
locales = ['ar', 'es', 'fr', 'ja', 'ko', 'pt-BR', 'ru', 'zh']
|
||||
for locale in locales:
|
||||
locale_dir = project_root / locale / 'get-started' / 'quickstarts'
|
||||
if not locale_dir.exists():
|
||||
print(f" - {locale}: no quickstarts directory, skipped")
|
||||
continue
|
||||
print(f"\nScanning {locale_dir}...")
|
||||
locale_quickstarts, locale_failures = build_quickstarts(
|
||||
locale_dir, project_root, update_badges=False, staged=staged)
|
||||
failures += locale_failures
|
||||
if not locale_quickstarts:
|
||||
print(f" - {locale}: no valid quick-start data, skipped")
|
||||
continue
|
||||
# Keep useCases/products canonical English: the grid filters match data
|
||||
# values against its option lists by string equality, and the
|
||||
# translation pipeline translates frontmatter tag values inconsistently.
|
||||
english_by_id = {qs['id']: qs for qs in quickstarts}
|
||||
for entry in locale_quickstarts:
|
||||
english = english_by_id.get(entry['id'])
|
||||
if english is None:
|
||||
# A localized page whose id has no English counterpart cannot
|
||||
# inherit canonical tags, so it would keep its own translated
|
||||
# useCases/products — which slugify_tag collapses to "" for
|
||||
# non-Latin text, shipping a card with an unfilterable tag.
|
||||
# Fail closed rather than emit broken explorer data (the
|
||||
# non-zero failure count below blocks all writes).
|
||||
print(f" ✗ {locale}/{entry['id']}: no English quickstart with "
|
||||
"this id; the localized tags cannot be made canonical. "
|
||||
"Rename this page to match its English counterpart, or "
|
||||
"add the missing English page.")
|
||||
failures += 1
|
||||
continue
|
||||
entry['useCases'] = english['useCases']
|
||||
entry['products'] = english['products']
|
||||
locale_output = (project_root / 'snippets' / locale / 'components'
|
||||
/ 'QuickStartsGrid' / 'quickstarts-data.jsx')
|
||||
staged[locale_output] = render_data_module(locale_quickstarts)
|
||||
print(f"✓ Staged {len(locale_quickstarts)} quick-start(s) for {locale_output}")
|
||||
|
||||
if failures:
|
||||
print(f"\n✗ {failures} page(s) failed; nothing was written — "
|
||||
"fix the errors above and re-run.")
|
||||
return 1
|
||||
|
||||
for path, content in staged.items():
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding='utf-8')
|
||||
print(f"\n✓ Wrote {len(staged)} file(s)")
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
||||
---
|
||||
config:
|
||||
default: false
|
||||
MD007: true # Unordered list indentation
|
||||
MD010: true # Disallow hard tabs
|
||||
MD012: true # Multiple consecutive blank lines
|
||||
MD040: true # Fenced code blocks should have a language specified
|
||||
custom-anchor-headings: true # Headings must have a custom anchor which is unique per page eg. # A Heading {#a-heading}
|
||||
ignores:
|
||||
- "docs/en/"
|
||||
- "changelogs/"
|
||||
customRules:
|
||||
- "./markdownlint/custom_rules/headings_have_custom_anchors.js"
|
||||
@@ -1,9 +0,0 @@
|
||||
StylesPath = vale
|
||||
|
||||
[!docs/releases/**/*.{md,mdx}]
|
||||
|
||||
[docs/**/*.{md,mdx}]
|
||||
BasedOnStyles = ClickHouse
|
||||
|
||||
ClickHouse.SentenceLength = NO
|
||||
ClickHouse.FutureTense = NO
|
||||
@@ -1,50 +0,0 @@
|
||||
const {filterTokens} = require('./utility_functions.js');
|
||||
|
||||
/*
|
||||
A custom rule that requires headings to have an explicit heading id which is unique per page.
|
||||
|
||||
E.g. ### Hello World {#my-explicit-id}
|
||||
|
||||
This is required as LLM translations break anchors by translating the heading.
|
||||
|
||||
The explicit ids (anchors) should be unique. E.g the following is invalid:
|
||||
|
||||
### First Heading {#my-explicit-id}
|
||||
|
||||
### Second Heading {#my-explicit-id} <--- should be unique
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
names: ['custom-anchor-headings'],
|
||||
tags: ["headings"],
|
||||
description: 'Headings should have an explicit heading id',
|
||||
function: (params, onError) => {
|
||||
const headingIds = {};
|
||||
filterTokens(params, "heading_open", (token) => {
|
||||
if (token.markup === "#") {
|
||||
return;
|
||||
}
|
||||
const headingLine = params.lines[token.map[0]];
|
||||
const match = /\{#([a-zA-Z0-9_-]+)\}/.exec(headingLine);
|
||||
|
||||
if (match) {
|
||||
const id = match[1];
|
||||
if (headingIds[id]) {
|
||||
onError({
|
||||
lineNumber: token.map[0] + 1,
|
||||
detail: `Duplicate heading id: '${id}'.`,
|
||||
context: headingLine,
|
||||
});
|
||||
} else {
|
||||
headingIds[id] = true;
|
||||
}
|
||||
} else {
|
||||
onError({
|
||||
lineNumber: token.map[0] + 1, // Line number is 1-based
|
||||
detail: `${headingLine.trim()} is missing an explicit ID (e.g., {#my-custom-id}).`,
|
||||
context: headingLine,
|
||||
});
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
// Calls the provided function for each matching token.
|
||||
const filterTokens = (params, type, handler) => {
|
||||
for (const token of params.parsers.markdownit.tokens) {
|
||||
if (token.type === type) {
|
||||
handler(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
filterTokens: filterTokens,
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.Ability
|
||||
description: |
|
||||
Focus on the feature, not the user's capabilities.
|
||||
extends: existence
|
||||
message: "Try to replace ('%s') with more precise language, unless this content is about security. See the word list for details."
|
||||
ignorecase: true
|
||||
vocab: false
|
||||
level: suggestion
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/word_list/#ability-able
|
||||
tokens:
|
||||
- ability to
|
||||
- ability
|
||||
- able to
|
||||
- able
|
||||
@@ -1,109 +0,0 @@
|
||||
extends: existence
|
||||
message: "ClickHouse formats '%s' should be enclosed in backticks."
|
||||
level: suggestion
|
||||
ignorecase: true
|
||||
tokens:
|
||||
-'One'
|
||||
-'ParquetMetadata'
|
||||
-'MySQLDump'
|
||||
-'CapnProto'
|
||||
-'PostgreSQLWire'
|
||||
-'TSV'
|
||||
-'PrettyCompactMonoBlock'
|
||||
-'JSONEachRowWithProgress'
|
||||
-'JSONCompactStrings'
|
||||
-'JSONStringsEachRowWithProgress'
|
||||
-'Vertical'
|
||||
-'PrettySpaceNoEscapesMonoBlock'
|
||||
-'Null'
|
||||
-'PrettySpaceNoEscapes'
|
||||
-'Markdown'
|
||||
-'Form'
|
||||
-'ODBCDriver2'
|
||||
-'PrettySpaceMonoBlock'
|
||||
-'PrettyCompactNoEscapesMonoBlock'
|
||||
-'PrettyCompactNoEscapes'
|
||||
-'JSONStringsEachRow'
|
||||
-'PrettyCompact'
|
||||
-'Values'
|
||||
-'PrettyNoEscapesMonoBlock'
|
||||
-'PrettyNoEscapes'
|
||||
-'PrettyMonoBlock'
|
||||
-'AvroConfluent'
|
||||
-'RawBLOB'
|
||||
-'Parquet'
|
||||
-'JSONCompactEachRowWithProgress'
|
||||
-'CustomSeparatedIgnoreSpacesWithNamesAndTypes'
|
||||
-'CustomSeparatedIgnoreSpacesWithNames'
|
||||
-'CustomSeparatedWithNamesAndTypes'
|
||||
-'PrettySpace'
|
||||
-'Arrow'
|
||||
-'JSONAsString'
|
||||
-'CustomSeparatedWithNames'
|
||||
-'CustomSeparated'
|
||||
-'BSONEachRow'
|
||||
-'Template'
|
||||
-'XML'
|
||||
-'ProtobufList'
|
||||
-'ProtobufSingle'
|
||||
-'JSONColumnsWithMetadata'
|
||||
-'TSVRawWithNamesAndTypes'
|
||||
-'Npy'
|
||||
-'TemplateIgnoreSpaces'
|
||||
-'JSONCompactColumns'
|
||||
-'JSONColumns'
|
||||
-'ArrowStream'
|
||||
-'JSONObjectEachRow'
|
||||
-'NDJSON'
|
||||
-'PrettyNDJSON'
|
||||
-'LineAsStringWithNamesAndTypes'
|
||||
-'PrettyJSONLines'
|
||||
-'CSVWithNamesAndTypes'
|
||||
-'ORC'
|
||||
-'Protobuf'
|
||||
-'PrettyJSONEachRow'
|
||||
-'JSONStrings'
|
||||
-'JSON'
|
||||
-'RawWithNames'
|
||||
-'MsgPack'
|
||||
-'LineAsStringWithNames'
|
||||
-'JSONCompactStringsEachRowWithProgress'
|
||||
-'LineAsString'
|
||||
-'RowBinaryWithNames'
|
||||
-'RowBinary'
|
||||
-'SQLInsert'
|
||||
-'Native'
|
||||
-'Prometheus'
|
||||
-'TSVRaw'
|
||||
-'JSONLines'
|
||||
-'CustomSeparatedIgnoreSpaces'
|
||||
-'JSONAsObject'
|
||||
-'HiveText'
|
||||
-'RowBinaryWithDefaults'
|
||||
-'JSONCompactStringsEachRowWithNamesAndTypes'
|
||||
-'RawWithNamesAndTypes'
|
||||
-'JSONCompactStringsEachRow'
|
||||
-'TabSeparatedWithNamesAndTypes'
|
||||
-'JSONCompactStringsEachRowWithNames'
|
||||
-'JSONCompactEachRowWithNamesAndTypes'
|
||||
-'JSONCompactEachRow'
|
||||
-'TSKV'
|
||||
-'Avro'
|
||||
-'JSONEachRow'
|
||||
-'Pretty'
|
||||
-'TSVRawWithNames'
|
||||
-'TabSeparatedRaw'
|
||||
-'Regexp'
|
||||
-'TabSeparatedRawWithNamesAndTypes'
|
||||
-'MySQLWire'
|
||||
-'CSVWithNames'
|
||||
-'Raw'
|
||||
-'TabSeparatedRawWithNames'
|
||||
-'RowBinaryWithNamesAndTypes'
|
||||
-'JSONCompact'
|
||||
-'CSV'
|
||||
-'JSONCompactEachRowWithNames'
|
||||
-'TSVWithNamesAndTypes'
|
||||
-'TabSeparatedWithNames'
|
||||
-'TabSeparated'
|
||||
-'TSVWithNames'
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,204 +0,0 @@
|
||||
extends: existence
|
||||
message: "ClickHouse server settings '%s' should be enclosed in backticks."
|
||||
level: suggestion
|
||||
ignorecase: true
|
||||
tokens:
|
||||
-'dictionary_background_reconnect_interval'
|
||||
-'show_addresses_in_stack_traces'
|
||||
-'shutdown_wait_unfinished_queries'
|
||||
-'shutdown_wait_unfinished'
|
||||
-'max_thread_pool_size'
|
||||
-'max_thread_pool_free_size'
|
||||
-'thread_pool_queue_size'
|
||||
-'max_io_thread_pool_size'
|
||||
-'max_io_thread_pool_free_size'
|
||||
-'io_thread_pool_queue_size'
|
||||
-'max_prefixes_deserialization_thread_pool_size'
|
||||
-'max_prefixes_deserialization_thread_pool_free_size'
|
||||
-'prefixes_deserialization_thread_pool_thread_pool_queue_size'
|
||||
-'max_fetch_partition_thread_pool_size'
|
||||
-'max_active_parts_loading_thread_pool_size'
|
||||
-'max_outdated_parts_loading_thread_pool_size'
|
||||
-'max_unexpected_parts_loading_thread_pool_size'
|
||||
-'max_parts_cleaning_thread_pool_size'
|
||||
-'max_mutations_bandwidth_for_server'
|
||||
-'max_merges_bandwidth_for_server'
|
||||
-'max_replicated_fetches_network_bandwidth_for_server'
|
||||
-'max_replicated_sends_network_bandwidth_for_server'
|
||||
-'max_remote_read_network_bandwidth_for_server'
|
||||
-'max_remote_write_network_bandwidth_for_server'
|
||||
-'max_local_read_bandwidth_for_server'
|
||||
-'max_local_write_bandwidth_for_server'
|
||||
-'max_backups_io_thread_pool_size'
|
||||
-'max_backups_io_thread_pool_free_size'
|
||||
-'backups_io_thread_pool_queue_size'
|
||||
-'backup_threads'
|
||||
-'max_backup_bandwidth_for_server'
|
||||
-'restore_threads'
|
||||
-'shutdown_wait_backups_and_restores'
|
||||
-'cannot_allocate_thread_fault_injection_probability'
|
||||
-'max_connections'
|
||||
-'asynchronous_metrics_update_period_s'
|
||||
-'asynchronous_metrics_enable_heavy_metrics'
|
||||
-'asynchronous_heavy_metrics_update_period_s'
|
||||
-'default_database'
|
||||
-'tmp_policy'
|
||||
-'max_temporary_data_on_disk_size'
|
||||
-'temporary_data_in_cache'
|
||||
-'aggregate_function_group_array_max_element_size'
|
||||
-'aggregate_function_group_array_action_when_limit_is_reached'
|
||||
-'max_server_memory_usage'
|
||||
-'max_server_memory_usage_to_ram_ratio'
|
||||
-'merges_mutations_memory_usage_soft_limit'
|
||||
-'merges_mutations_memory_usage_to_ram_ratio'
|
||||
-'allow_use_jemalloc_memory'
|
||||
-'cgroups_memory_usage_observer_wait_time'
|
||||
-'cgroup_memory_watcher_hard_limit_ratio'
|
||||
-'cgroup_memory_watcher_soft_limit_ratio'
|
||||
-'async_insert_threads'
|
||||
-'async_insert_queue_flush_on_shutdown'
|
||||
-'ignore_empty_sql_security_in_create_view_query'
|
||||
-'max_build_vector_similarity_index_thread_pool_size'
|
||||
-'database_atomic_delay_before_drop_table_sec'
|
||||
-'database_catalog_unused_dir_hide_timeout_sec'
|
||||
-'database_catalog_unused_dir_rm_timeout_sec'
|
||||
-'database_catalog_unused_dir_cleanup_period_sec'
|
||||
-'database_catalog_drop_error_cooldown_sec'
|
||||
-'database_catalog_drop_table_concurrency'
|
||||
-'max_concurrent_queries'
|
||||
-'max_concurrent_insert_queries'
|
||||
-'max_concurrent_select_queries'
|
||||
-'max_waiting_queries'
|
||||
-'cache_size_to_ram_max_ratio'
|
||||
-'uncompressed_cache_policy'
|
||||
-'uncompressed_cache_size'
|
||||
-'uncompressed_cache_size_ratio'
|
||||
-'mark_cache_policy'
|
||||
-'mark_cache_size'
|
||||
-'mark_cache_size_ratio'
|
||||
-'mark_cache_prewarm_ratio'
|
||||
-'primary_index_cache_policy'
|
||||
-'primary_index_cache_size'
|
||||
-'primary_index_cache_size_ratio'
|
||||
-'primary_index_cache_prewarm_ratio'
|
||||
-'vector_similarity_index_cache_policy'
|
||||
-'vector_similarity_index_cache_size'
|
||||
-'vector_similarity_index_cache_max_entries'
|
||||
-'vector_similarity_index_cache_size_ratio'
|
||||
-'index_uncompressed_cache_policy'
|
||||
-'index_uncompressed_cache_size'
|
||||
-'index_uncompressed_cache_size_ratio'
|
||||
-'index_mark_cache_policy'
|
||||
-'index_mark_cache_size'
|
||||
-'index_mark_cache_size_ratio'
|
||||
-'page_cache_block_size'
|
||||
-'page_cache_history_window_ms'
|
||||
-'page_cache_policy'
|
||||
-'page_cache_size_ratio'
|
||||
-'page_cache_min_size'
|
||||
-'page_cache_max_size'
|
||||
-'page_cache_free_memory_ratio'
|
||||
-'page_cache_lookahead_blocks'
|
||||
-'page_cache_shards'
|
||||
-'mmap_cache_size'
|
||||
-'compiled_expression_cache_size'
|
||||
-'compiled_expression_cache_elements_size'
|
||||
-'query_condition_cache_policy'
|
||||
-'query_condition_cache_size'
|
||||
-'query_condition_cache_size_ratio'
|
||||
-'disable_internal_dns_cache'
|
||||
-'dns_cache_max_entries'
|
||||
-'dns_cache_update_period'
|
||||
-'dns_max_consecutive_failures'
|
||||
-'dns_allow_resolve_names_to_ipv4'
|
||||
-'dns_allow_resolve_names_to_ipv6'
|
||||
-'max_table_size_to_drop'
|
||||
-'max_partition_size_to_drop'
|
||||
-'max_table_num_to_warn'
|
||||
-'max_pending_mutations_to_warn'
|
||||
-'max_view_num_to_warn'
|
||||
-'max_dictionary_num_to_warn'
|
||||
-'max_database_num_to_warn'
|
||||
-'max_part_num_to_warn'
|
||||
-'max_table_num_to_throw'
|
||||
-'max_replicated_table_num_to_throw'
|
||||
-'max_dictionary_num_to_throw'
|
||||
-'max_view_num_to_throw'
|
||||
-'max_database_num_to_throw'
|
||||
-'max_authentication_methods_per_user'
|
||||
-'concurrent_threads_soft_limit_num'
|
||||
-'concurrent_threads_soft_limit_ratio_to_cores'
|
||||
-'concurrent_threads_scheduler'
|
||||
-'background_pool_size'
|
||||
-'background_merges_mutations_concurrency_ratio'
|
||||
-'background_merges_mutations_scheduling_policy'
|
||||
-'background_move_pool_size'
|
||||
-'background_fetches_pool_size'
|
||||
-'background_common_pool_size'
|
||||
-'background_buffer_flush_schedule_pool_size'
|
||||
-'background_schedule_pool_size'
|
||||
-'background_message_broker_schedule_pool_size'
|
||||
-'background_distributed_schedule_pool_size'
|
||||
-'tables_loader_foreground_pool_size'
|
||||
-'tables_loader_background_pool_size'
|
||||
-'async_load_databases'
|
||||
-'async_load_system_database'
|
||||
-'display_secrets_in_show_and_select'
|
||||
-'keep_alive_timeout'
|
||||
-'max_keep_alive_requests'
|
||||
-'replicated_fetches_http_connection_timeout'
|
||||
-'replicated_fetches_http_send_timeout'
|
||||
-'replicated_fetches_http_receive_timeout'
|
||||
-'total_memory_profiler_step'
|
||||
-'total_memory_tracker_sample_probability'
|
||||
-'total_memory_profiler_sample_min_allocation_size'
|
||||
-'total_memory_profiler_sample_max_allocation_size'
|
||||
-'validate_tcp_client_information'
|
||||
-'storage_metadata_write_full_object_key'
|
||||
-'max_materialized_views_count_for_table'
|
||||
-'max_database_replicated_create_table_thread_pool_size'
|
||||
-'database_replicated_allow_detach_permanently'
|
||||
-'format_alter_operations_with_parentheses'
|
||||
-'default_replica_path'
|
||||
-'default_replica_name'
|
||||
-'disk_connections_soft_limit'
|
||||
-'disk_connections_warn_limit'
|
||||
-'disk_connections_store_limit'
|
||||
-'storage_connections_soft_limit'
|
||||
-'storage_connections_warn_limit'
|
||||
-'storage_connections_store_limit'
|
||||
-'http_connections_soft_limit'
|
||||
-'http_connections_warn_limit'
|
||||
-'http_connections_store_limit'
|
||||
-'global_profiler_real_time_period_ns'
|
||||
-'global_profiler_cpu_time_period_ns'
|
||||
-'enable_azure_sdk_logging'
|
||||
-'max_entries_for_hash_table_stats'
|
||||
-'merge_workload'
|
||||
-'mutation_workload'
|
||||
-'throw_on_unknown_workload'
|
||||
-'series_keeper_path'
|
||||
-'prepare_system_log_tables_on_startup'
|
||||
-'config_reload_interval_ms'
|
||||
-'memory_worker_period_ms'
|
||||
-'memory_worker_correct_memory_tracker'
|
||||
-'memory_worker_use_cgroup'
|
||||
-'disable_insertion_and_mutation'
|
||||
-'parts_kill_delay_period'
|
||||
-'parts_kill_delay_period_random_add'
|
||||
-'parts_killer_pool_size'
|
||||
-'keeper_multiread_batch_size'
|
||||
-'use_legacy_mongodb_integration'
|
||||
-'license_key'
|
||||
-'prefetch_threadpool_pool_size'
|
||||
-'prefetch_threadpool_queue_size'
|
||||
-'load_marks_threadpool_pool_size'
|
||||
-'load_marks_threadpool_queue_size'
|
||||
-'threadpool_writer_pool_size'
|
||||
-'threadpool_writer_queue_size'
|
||||
-'iceberg_catalog_threadpool_pool_size'
|
||||
-'iceberg_catalog_threadpool_queue_size'
|
||||
-'allow_feature_tier'
|
||||
-'dictionaries_lazy_load'
|
||||
-'wait_dictionaries_load_at_startup'
|
||||
-'storage_shared_set_join_use_inner_uuid'
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,70 +0,0 @@
|
||||
extends: existence
|
||||
message: "ClickHouse table engines '%s' should be enclosed in backticks."
|
||||
level: suggestion
|
||||
ignorecase: true
|
||||
tokens:
|
||||
-'OSS'
|
||||
-'AzureBlobStorage'
|
||||
-'KeeperMap'
|
||||
-'S3'
|
||||
-'MaterializedPostgreSQL'
|
||||
-'PostgreSQL'
|
||||
-'NATS'
|
||||
-'COSN'
|
||||
-'Redis'
|
||||
-'MongoDB'
|
||||
-'MySQL'
|
||||
-'S3Queue'
|
||||
-'Distributed'
|
||||
-'ReplicatedSummingMergeTree'
|
||||
-'ExecutablePool'
|
||||
-'IcebergLocal'
|
||||
-'ODBC'
|
||||
-'Hive'
|
||||
-'View'
|
||||
-'FuzzQuery'
|
||||
-'Loop'
|
||||
-'MaterializedView'
|
||||
-'Join'
|
||||
-'Executable'
|
||||
-'Kafka'
|
||||
-'Set'
|
||||
-'Dictionary'
|
||||
-'URL'
|
||||
-'ReplicatedVersionedCollapsingMergeTree'
|
||||
-'File'
|
||||
-'IcebergS3'
|
||||
-'Iceberg'
|
||||
-'GenerateRandom'
|
||||
-'LiveView'
|
||||
-'Memory'
|
||||
-'Merge'
|
||||
-'WindowView'
|
||||
-'Null'
|
||||
-'SQLite'
|
||||
-'VersionedCollapsingMergeTree'
|
||||
-'ReplacingMergeTree'
|
||||
-'TimeSeries'
|
||||
-'HDFS'
|
||||
-'EmbeddedRocksDB'
|
||||
-'ReplicatedCollapsingMergeTree'
|
||||
-'IcebergHDFS'
|
||||
-'ReplicatedGraphiteMergeTree'
|
||||
-'IcebergAzure'
|
||||
-'ReplicatedMergeTree'
|
||||
-'Hudi'
|
||||
-'TinyLog'
|
||||
-'FuzzJSON'
|
||||
-'ReplicatedReplacingMergeTree'
|
||||
-'JDBC'
|
||||
-'SummingMergeTree'
|
||||
-'MergeTree'
|
||||
-'GraphiteMergeTree'
|
||||
-'CollapsingMergeTree'
|
||||
-'ReplicatedAggregatingMergeTree'
|
||||
-'RabbitMQ'
|
||||
-'Buffer'
|
||||
-'AzureQueue'
|
||||
-'AggregatingMergeTree'
|
||||
-'StripeLog'
|
||||
-'Log'
|
||||
@@ -1,64 +0,0 @@
|
||||
extends: existence
|
||||
message: "ClickHouse table engines '%s' should be enclosed in backticks."
|
||||
level: suggestion
|
||||
ignorecase: true
|
||||
tokens:
|
||||
-'hudiCluster'
|
||||
-'icebergAzureCluster'
|
||||
-'icebergS3Cluster'
|
||||
-'icebergLocal'
|
||||
-'icebergHDFS'
|
||||
-'icebergAzure'
|
||||
-'icebergS3'
|
||||
-'iceberg'
|
||||
-'hdfsCluster'
|
||||
-'azureBlobStorageCluster'
|
||||
-'oss'
|
||||
-'timeSeriesMetrics'
|
||||
-'fuzzQuery'
|
||||
-'timeSeriesData'
|
||||
-'format'
|
||||
-'loop'
|
||||
-'postgresql'
|
||||
-'clusterAllReplicas'
|
||||
-'viewIfPermitted'
|
||||
-'view'
|
||||
-'jdbc'
|
||||
-'icebergHDFSCluster'
|
||||
-'s3Cluster'
|
||||
-'timeSeriesTags'
|
||||
-'hive'
|
||||
-'hdfs'
|
||||
-'dictionary'
|
||||
-'numbers_mt'
|
||||
-'mergeTreeIndex'
|
||||
-'hudi'
|
||||
-'s3'
|
||||
-'cosn'
|
||||
-'generateRandom'
|
||||
-'merge'
|
||||
-'url'
|
||||
-'zeros_mt'
|
||||
-'executable'
|
||||
-'fuzzJSON'
|
||||
-'urlCluster'
|
||||
-'fileCluster'
|
||||
-'values'
|
||||
-'file'
|
||||
-'redis'
|
||||
-'zeros'
|
||||
-'input'
|
||||
-'mysql'
|
||||
-'odbc'
|
||||
-'generateSeries'
|
||||
-'mongodb'
|
||||
-'generate_series'
|
||||
-'sqlite'
|
||||
-'cluster'
|
||||
-'gcs'
|
||||
-'remoteSecure'
|
||||
-'numbers'
|
||||
-'azureBlobStorage'
|
||||
-'viewExplain'
|
||||
-'null'
|
||||
-'remote'
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.BadPlurals
|
||||
description: |
|
||||
Don't write plural words with the '(s)' construction. 'HTTP(S)' is acceptable.
|
||||
extends: existence
|
||||
message: "Rewrite '%s' to be plural without parentheses."
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/word_list/#s
|
||||
vocab: false
|
||||
level: warning
|
||||
ignorecase: true
|
||||
nonword: true
|
||||
tokens:
|
||||
- '(?<!http)\(s\)'
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.British
|
||||
description: |
|
||||
Checks that US spelling is used instead of British spelling.
|
||||
extends: substitution
|
||||
message: "Use the US spelling '%s' instead of the British '%s'."
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/#language
|
||||
vocab: false
|
||||
level: warning
|
||||
action:
|
||||
name: replace
|
||||
ignorecase: true
|
||||
swap:
|
||||
aeon: eon
|
||||
aeroplane: airplane
|
||||
ageing: aging
|
||||
aluminium: aluminum
|
||||
anaemia: anemia
|
||||
anaesthesia: anesthesia
|
||||
analyse: analyze
|
||||
annexe: annex
|
||||
apologise: apologize
|
||||
authorise: authorize
|
||||
authorised: authorized
|
||||
authorisation: authorization
|
||||
authorising: authorizing
|
||||
behaviour: behavior
|
||||
busses: buses
|
||||
calibre: caliber
|
||||
categorise: categorize
|
||||
categorised: categorized
|
||||
categorises: categorizes
|
||||
categorising: categorizing
|
||||
centre: center
|
||||
cheque: check
|
||||
civilisation: civilization
|
||||
civilise: civilize
|
||||
colour: color
|
||||
cosy: cozy
|
||||
cypher: cipher
|
||||
dependant: dependent
|
||||
defence: defense
|
||||
distil: distill
|
||||
draught: draft
|
||||
encyclopaedia: encyclopedia
|
||||
enquiry: inquiry
|
||||
enrol: enroll
|
||||
enrolment: enrollment
|
||||
enthral: enthrall
|
||||
# equalled: equaled // Under discussion
|
||||
# equalling: equaling // Under discussion
|
||||
favourite: favorite
|
||||
fibre: fiber
|
||||
fillet: filet
|
||||
flavour: flavor
|
||||
furore: furor
|
||||
fulfil: fulfill
|
||||
gaol: jail
|
||||
grey: gray
|
||||
humour: humor
|
||||
honour: honor
|
||||
initialled: initialed
|
||||
initialling: initialing
|
||||
instil: instill
|
||||
jewellery: jewelry
|
||||
labelling: labeling
|
||||
labelled: labeled
|
||||
labour: labor
|
||||
libellous: libelous
|
||||
licence: license
|
||||
likeable: likable
|
||||
liveable: livable
|
||||
lustre: luster
|
||||
manoeuvre: maneuver
|
||||
marvellous: marvelous
|
||||
matt: matte
|
||||
meagre: meager
|
||||
metre: meter
|
||||
modelling: modeling
|
||||
moustache: mustache
|
||||
neighbour: neighbor
|
||||
normalise: normalize
|
||||
offence: offense
|
||||
optimise: optimize
|
||||
optimised: optimized
|
||||
optimising: optimizing
|
||||
organise: organize
|
||||
orientated: oriented
|
||||
paralyse: paralyze
|
||||
plough: plow
|
||||
pretence: pretense
|
||||
programme: program
|
||||
pyjamas: pajamas
|
||||
rateable: ratable
|
||||
realise: realize
|
||||
recognise: recognize
|
||||
reconnoitre: reconnoiter
|
||||
rumour: rumor
|
||||
sabre: saber
|
||||
saleable: salable
|
||||
saltpetre: saltpeter
|
||||
sceptic: skeptic
|
||||
sepulchre: sepulcher
|
||||
signalling: signaling
|
||||
sizeable: sizable
|
||||
skilful: skillful
|
||||
sombre: somber
|
||||
smoulder: smolder
|
||||
speciality: specialty
|
||||
spectre: specter
|
||||
splendour: splendor
|
||||
standardise: standardize
|
||||
standardised: standardized
|
||||
sulphur: sulfur
|
||||
theatre: theater
|
||||
travelled: traveled
|
||||
traveller: traveler
|
||||
travelling: traveling
|
||||
unshakeable: unshakable
|
||||
wilful: willful
|
||||
yoghurt: yogurt
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.CodeblockFences
|
||||
description: |
|
||||
Ensures all codeblock language tags use the full name, not aliases.
|
||||
extends: existence
|
||||
message: "Instead of '%s' for the code block, use yaml, ruby, plaintext, markdown, javascript, shell, go, python, dockerfile, or typescript."
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/#code-blocks
|
||||
vocab: false
|
||||
level: suggestion
|
||||
scope: raw
|
||||
raw:
|
||||
- '\`\`\`(yml|rb|text|md|bash|sh\n|js\n|golang\n|py\n|docker\n|ts|irb)'
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: existence
|
||||
message: "'%s' should be in lowercase."
|
||||
link: 'https://developers.google.com/style/colons'
|
||||
nonword: true
|
||||
level: warning
|
||||
scope: sentence
|
||||
tokens:
|
||||
- '(?<!:[^ ]+?):\s[A-Z]'
|
||||
@@ -1,30 +0,0 @@
|
||||
extends: substitution
|
||||
message: "Use '%s' instead of '%s'."
|
||||
link: 'https://developers.google.com/style/contractions'
|
||||
level: suggestion
|
||||
ignorecase: true
|
||||
action:
|
||||
name: replace
|
||||
swap:
|
||||
are not: aren't
|
||||
cannot: can't
|
||||
could not: couldn't
|
||||
did not: didn't
|
||||
do not: don't
|
||||
does not: doesn't
|
||||
has not: hasn't
|
||||
have not: haven't
|
||||
how is: how's
|
||||
is not: isn't
|
||||
it is: it's
|
||||
should not: shouldn't
|
||||
that is: that's
|
||||
they are: they're
|
||||
was not: wasn't
|
||||
we are: we're
|
||||
we have: we've
|
||||
were not: weren't
|
||||
what is: what's
|
||||
when is: when's
|
||||
where is: where's
|
||||
will not: won't
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.CurrentStatus
|
||||
description: |
|
||||
Checks for words that indicate a product or feature may change in the future.
|
||||
extends: existence
|
||||
message: "Remove '%s'. The documentation reflects the current state of the product."
|
||||
vocab: false
|
||||
level: warning
|
||||
ignorecase: true
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/#promising-features-in-future-versions
|
||||
tokens:
|
||||
- currently
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.EOLWhitespace
|
||||
description: |
|
||||
Checks that there is no useless whitespace at the end of lines.
|
||||
extends: existence
|
||||
message: "Remove whitespace characters from the end of the line."
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/
|
||||
vocab: false
|
||||
level: warning
|
||||
scope: raw
|
||||
raw:
|
||||
- ' +\n'
|
||||
@@ -1,12 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't use exclamation points in text."
|
||||
link: "https://developers.google.com/style/exclamation-points"
|
||||
nonword: true
|
||||
level: warning
|
||||
action:
|
||||
name: edit
|
||||
params:
|
||||
- trim_right
|
||||
- "!"
|
||||
tokens:
|
||||
- '\w+!(?:\s|$)'
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.FutureTense
|
||||
description: |
|
||||
Checks for use of future tense in sentences.
|
||||
Present tense is strongly preferred.
|
||||
extends: existence
|
||||
message: "Instead of future tense '%s', use present tense."
|
||||
ignorecase: true
|
||||
nonword: true
|
||||
vocab: false
|
||||
level: suggestion
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/word_list/#future-tense
|
||||
tokens:
|
||||
- (going to|will|won't)[ \n:]\w*
|
||||
- (It?|we|you|they)'ll[ \n:]\w*
|
||||
@@ -1,13 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't put a period at the end of a heading."
|
||||
link: "https://developers.google.com/style/capitalization#capitalization-in-titles-and-headings"
|
||||
nonword: true
|
||||
level: warning
|
||||
scope: heading
|
||||
action:
|
||||
name: edit
|
||||
params:
|
||||
- trim_right
|
||||
- "."
|
||||
tokens:
|
||||
- '[a-z0-9][.]\s*$'
|
||||
@@ -1,890 +0,0 @@
|
||||
extends: capitalization
|
||||
message: "'%s' should use sentence-style capitalization."
|
||||
link: "https://developers.google.com/style/capitalization#capitalization-in-titles-and-headings"
|
||||
level: warning
|
||||
scope: heading
|
||||
match: $sentence
|
||||
indicators:
|
||||
- ":"
|
||||
exceptions:
|
||||
- "^[a-z]+[A-Z][a-zA-Z]*$" # Matches camelCase pattern
|
||||
- "^[A-Z]+$" # Matches words that are entirely uppercase
|
||||
- "^[A-Z][a-zA-Z]*[A-Z][a-zA-Z]*$" # Matches PascalCase system metrics
|
||||
- "^[A-Z][a-zA-Z]*[A-Z][a-zA-Z]*_.*$" # Matches system metrics with underscores
|
||||
- "^[a-z][a-z_]*[a-z]$" # Matches configuration parameter names with underscores
|
||||
- API
|
||||
- ANN
|
||||
- APPEND
|
||||
- DELETE FROM
|
||||
- ALTER DELETE
|
||||
- DROP PARTITION
|
||||
- TRUNCATE
|
||||
- Arrow
|
||||
- AWS
|
||||
- AWS
|
||||
- Apache
|
||||
- AggregatingMergeTree
|
||||
- Amazon
|
||||
- Amazon Web Services
|
||||
- Azure
|
||||
- Azure Blob Storage
|
||||
- Azure Event Hubs
|
||||
- B2B
|
||||
- BigQuery
|
||||
- Bring Your Own Cloud
|
||||
- Bun
|
||||
- BYOC
|
||||
- BYOC
|
||||
- CPU
|
||||
- CSV
|
||||
- CDC
|
||||
- CMEK
|
||||
- CLI
|
||||
- ClickHouse
|
||||
- ClickHouse Cloud
|
||||
- ClickHouse Keeper
|
||||
- ClickPipe
|
||||
- ClickPipes
|
||||
- ClickPipes Connector
|
||||
- ClickStack
|
||||
- ClickStack
|
||||
- CloudFormation
|
||||
- Cosmos
|
||||
- Customer Managed Encryption Keys
|
||||
- DBT
|
||||
- DBT
|
||||
- DDL
|
||||
- DNS
|
||||
- Docker
|
||||
- Docker
|
||||
- Docker Compose
|
||||
- DigitalOcean Spaces
|
||||
- Duo SAML
|
||||
- EDOT
|
||||
- Elastic Agent
|
||||
- Emmet
|
||||
- FAQ
|
||||
- Filebeat
|
||||
- Frequently Asked Questions
|
||||
- GA
|
||||
- GCS
|
||||
- GET
|
||||
- GitHub
|
||||
- Go
|
||||
- Google
|
||||
- Google Cloud
|
||||
- Google Cloud Platform
|
||||
- Google Cloud Storage
|
||||
- HTTP
|
||||
- Helm
|
||||
- HyperDX
|
||||
- HyperDX
|
||||
- I
|
||||
- IdP
|
||||
- JWT
|
||||
- Java
|
||||
- JSON
|
||||
- JSON
|
||||
- JSON format settings
|
||||
- JSON settings
|
||||
- Kafka
|
||||
- Kafka
|
||||
- Kafka Connect
|
||||
- KMS
|
||||
- Kubernetes
|
||||
- LZ4
|
||||
- Linux
|
||||
- MERGETREE
|
||||
- Microsoft
|
||||
- Middle East
|
||||
- MongoDB
|
||||
- NPM
|
||||
- NodeJS
|
||||
- NodeJs
|
||||
- OLAP
|
||||
- OLTP
|
||||
- ORC
|
||||
- OSS
|
||||
- OTel
|
||||
- Okta
|
||||
- OpenTelemetry
|
||||
- Pandas
|
||||
- Parquet
|
||||
- PlanetScale
|
||||
- Postgres
|
||||
- PostgreSQL
|
||||
- PSC
|
||||
- Private Link
|
||||
- Private Service Connect
|
||||
- PrivateLink
|
||||
- Prometheus
|
||||
- Python
|
||||
- RBAC
|
||||
- REPL
|
||||
- REPLACE
|
||||
- REST
|
||||
- Role-Based Access Control
|
||||
- SAML
|
||||
- SOC
|
||||
- SQL
|
||||
- SQL
|
||||
- SSO
|
||||
- S3
|
||||
- SaaS
|
||||
- SDK
|
||||
- Studio
|
||||
- TCP
|
||||
- TDE
|
||||
- TTL
|
||||
- TiDB
|
||||
- Time To Live
|
||||
- Transparent Data Encryption
|
||||
- TypeScript
|
||||
- U.S.
|
||||
- UDF
|
||||
- UI
|
||||
- URLs
|
||||
- UAE
|
||||
- User Defined Functions
|
||||
- VLDB
|
||||
- VS
|
||||
- VPC
|
||||
- Vitess
|
||||
- Visual
|
||||
- Windows
|
||||
- Yandex.Metrica
|
||||
- ZSTD
|
||||
- chDB
|
||||
- ch_bucket_us_east1
|
||||
- ch_bucket_us_east4
|
||||
- gRPC
|
||||
- macOS
|
||||
- MergeTree
|
||||
- MySQL
|
||||
- ReplacingMergeTree
|
||||
- SLA
|
||||
- SLAs
|
||||
- Beta
|
||||
- Preview
|
||||
- Private Preview
|
||||
- CDC
|
||||
- DB
|
||||
- URL
|
||||
- Build
|
||||
- Testing
|
||||
- Packaging
|
||||
- Tier
|
||||
- Tiers
|
||||
- Overview
|
||||
- Console
|
||||
- Endpoints
|
||||
- Backups
|
||||
- Thresholds
|
||||
- Keys
|
||||
- Routing
|
||||
- Cloud
|
||||
- Change Data Capture
|
||||
- PostLinks
|
||||
- PostHistory
|
||||
- DateTime
|
||||
- Stack Overflow
|
||||
- Homebrew
|
||||
- WSL
|
||||
- London
|
||||
- LowCardinality
|
||||
- Query 2. Average price per year in London
|
||||
- V24.5 changelog for Cloud
|
||||
- V24.6 changelog for Cloud
|
||||
- SELECT
|
||||
- PARTITION BY
|
||||
- Common Table Expressions
|
||||
- FixedString(N)
|
||||
- AvgMerge
|
||||
- AvgMergeState
|
||||
- AvgState
|
||||
- CountResample
|
||||
- Avgmap
|
||||
- Avgmergestate
|
||||
- Avgstate
|
||||
- Countresample
|
||||
- Grouparrayresample
|
||||
- Grouparraydistinct
|
||||
- Maxmap
|
||||
- Minsimplestate
|
||||
- Minmap
|
||||
- Sumarray
|
||||
- Summap
|
||||
- Uniqarray
|
||||
- Sumsimplestate
|
||||
- CityHash
|
||||
- FixedString(N)
|
||||
- Avg merge
|
||||
- Avg merge state
|
||||
- Avg state
|
||||
- Count resample
|
||||
- avgMerge
|
||||
- avgMergeState
|
||||
- avgState
|
||||
- countResample
|
||||
- PREWHERE
|
||||
- Splunk
|
||||
- Retool
|
||||
- Airbyte
|
||||
- RawBLOB
|
||||
- RowBinary
|
||||
- MessagePack
|
||||
- Protocol Buffers
|
||||
- Cap'n Proto
|
||||
- TabSeparated
|
||||
- TabSeparatedRaw
|
||||
- TabSeparatedWithNames
|
||||
- TabSeparatedWithNamesAndTypes
|
||||
- TabSeparatedRawWithNames
|
||||
- TabSeparatedRawWithNamesAndTypes
|
||||
- CustomSeparated
|
||||
- CustomSeparatedWithNames
|
||||
- CustomSeparatedWithNamesAndTypes
|
||||
- BSONEachRow
|
||||
- PrettyNoEscapes
|
||||
- PrettyMonoBlock
|
||||
- PrettyNoEscapesMonoBlock
|
||||
- PrettyCompact
|
||||
- PrettyCompactNoEscapes
|
||||
- PrettyCompactMonoBlock
|
||||
- PrettyCompactNoEscapesMonoBlock
|
||||
- PrettySpace
|
||||
- PrettySpaceNoEscapes
|
||||
- PrettySpaceMonoBlock
|
||||
- PrettySpaceNoEscapesMonoBlock
|
||||
- CapnProto
|
||||
- ProtobufSingle
|
||||
- ProtobufList
|
||||
- AvroConfluent
|
||||
- MsgPack
|
||||
- predefined_query_handler
|
||||
- dynamic_query_handler
|
||||
- static
|
||||
- clickhouse-local
|
||||
- agx
|
||||
- ch-ui
|
||||
- HouseOps
|
||||
- LightHouse
|
||||
- qryn
|
||||
- clickhouse-cli
|
||||
- clickhouse-flamegraph
|
||||
- clickhouse-plantuml
|
||||
- xeus-clickhouse
|
||||
- ClickCat
|
||||
- ClickHouse-Mate
|
||||
- clickhouse-monitoring
|
||||
- CKibana
|
||||
- SeekTable
|
||||
- chproxy
|
||||
- KittenHouse
|
||||
- ClickHouse-Bulk
|
||||
- input_format_json_try_infer_numbers_from_strings
|
||||
- input_format_json_try_infer_named_tuples_from_objects
|
||||
- input_format_json_use_string_type_for_ambiguous_paths_in_named_tuples_inference_from_objects
|
||||
- input_format_json_read_objects_as_strings
|
||||
- input_format_json_read_numbers_as_strings
|
||||
- input_format_json_read_bools_as_numbers
|
||||
- input_format_json_read_bools_as_strings
|
||||
- input_format_json_read_arrays_as_strings
|
||||
- input_format_json_infer_incomplete_types_as_strings
|
||||
- input_format_csv_try_infer_numbers_from_strings
|
||||
- input_format_max_rows_to_read_for_schema_inference
|
||||
- input_format_max_bytes_to_read_for_schema_inference
|
||||
- column_names_for_schema_inference
|
||||
- schema_inference_hints
|
||||
- schema_inference_make_columns_nullable
|
||||
- input_format_try_infer_integers
|
||||
- input_format_try_infer_datetimes
|
||||
- input_format_try_infer_datetimes_only_datetime64
|
||||
- input_format_try_infer_dates
|
||||
- input_format_try_infer_exponent_floats
|
||||
- TSV
|
||||
- TSKV
|
||||
- format_template_row
|
||||
- format_template_rows_between_delimiter
|
||||
- format_template_resultset
|
||||
- DataLens
|
||||
- Avro
|
||||
- Yandex
|
||||
- Holistics
|
||||
- ClickHouse Schema Flow Visualizer
|
||||
- Holistics Software
|
||||
- UPDATEs
|
||||
- DELETEs
|
||||
- INSERTs
|
||||
- MERGEs
|
||||
- DBT
|
||||
- insert_overwrite
|
||||
- Vector
|
||||
- Nginx
|
||||
- Dataflow
|
||||
- QueryResult
|
||||
- AsyncClient
|
||||
- QueryContexts
|
||||
- StreamContexts
|
||||
- Easypanel
|
||||
- Date32
|
||||
- Node.js
|
||||
- EventHubs
|
||||
- Kafka Connect Sink
|
||||
- String
|
||||
- Map
|
||||
- Nested
|
||||
- flatten_nested
|
||||
- Tableau Online
|
||||
- SSL/TLS
|
||||
- Delta Lake
|
||||
- Embeddable
|
||||
- Draxlr
|
||||
- Rust
|
||||
- Rockset
|
||||
- Astrato
|
||||
- Chartbrew
|
||||
- Deepnote
|
||||
- Zing Data
|
||||
- Mitzu
|
||||
- Tableau
|
||||
- DataGrip
|
||||
- Grafana
|
||||
- Datadog
|
||||
- Splunk Enterprise
|
||||
- Explo
|
||||
- Hashboard
|
||||
- Luzmo
|
||||
- QuickSight
|
||||
- Metabase
|
||||
- Superset
|
||||
- clickhouse-static-files-disk-uploader
|
||||
- OpenTelemetry
|
||||
- Kubernetes
|
||||
- Data Lake
|
||||
- Unity
|
||||
- Databricks
|
||||
- Read Delta
|
||||
- Google Cloud Run
|
||||
- ClickStack
|
||||
- Build-Your-Own
|
||||
- Time-Series
|
||||
- New Features
|
||||
- Bug Fixes
|
||||
- Bug Fix
|
||||
- Backward Incompatible Changes
|
||||
- Backward Incompatible Change
|
||||
- Build Improvements
|
||||
- Main Changes
|
||||
- New Feature
|
||||
- Experimental Feature
|
||||
- Security Fix
|
||||
- Performance Improvements
|
||||
- Performance Improvement
|
||||
- Documentation Updates
|
||||
- Documentation Update
|
||||
- Improved Workflow for Developing and Assembling ClickHouse
|
||||
- Please Note When Upgrading
|
||||
- Complete List of Changes
|
||||
- Minor Changes
|
||||
- ClickHouse Release
|
||||
- Improvements
|
||||
- Build Changes
|
||||
- Experimental Features
|
||||
- Table of Contents
|
||||
- Upgrade Notes
|
||||
- CVE-2021-42390
|
||||
- CVE-2021-42391
|
||||
- CVE-2021-25263
|
||||
- CVE-2019-15024
|
||||
- CVE-2019-16535
|
||||
- CVE-2019-16536
|
||||
- CVE-2019-18657
|
||||
- CVE-2018-14672
|
||||
- CVE-2018-14671
|
||||
- CVE-2018-14668
|
||||
- CVE-2018-14669
|
||||
- CVE-2018-14670
|
||||
- Performance Optimizations
|
||||
- Code Cleanup
|
||||
- Cloud Changelog
|
||||
- CVE-2025-1385
|
||||
- CVE-2024-6873
|
||||
- CVE-2024-22412
|
||||
- CVE-2023-47118
|
||||
- CVE-2023-48298
|
||||
- CVE-2023-48704
|
||||
- CVE-2022-44011
|
||||
- CVE-2022-44010
|
||||
- CVE-2021-43304
|
||||
- CVE-2021-43305
|
||||
- CVE-2021-42387
|
||||
- CVE-2021-42388
|
||||
- CVE-2021-42389
|
||||
- cmake-variants.yaml
|
||||
- launch.json
|
||||
- settings.json
|
||||
- run-debug.sh
|
||||
- tasks.json
|
||||
- Third-Party Libraries
|
||||
- Build Clickhouse with DEFLATE_QPL
|
||||
- Run Benchmark with DEFLATE_QPL
|
||||
- Rust Libraries
|
||||
- DEFLATE_QPL
|
||||
- Star Schema
|
||||
- DockerHub
|
||||
- cpp
|
||||
- codespell
|
||||
- aspell
|
||||
- mypy
|
||||
- AST
|
||||
- "C\\+\\+"
|
||||
- clang-tidy
|
||||
- DataLakeCatalog
|
||||
- MergeTree
|
||||
- ReplacingMergeTree
|
||||
- SummingMergeTree
|
||||
- AggregatingMergeTree
|
||||
- CollapsingMergeTree
|
||||
- VersionedCollapsingMergeTree
|
||||
- GraphiteMergeTree
|
||||
- TinyLog
|
||||
- StripeLog
|
||||
- MaterializedView
|
||||
- DROP/DETACH TABLE
|
||||
- ReplicatedMergeTree
|
||||
- AzureQueue
|
||||
- DeltaLake
|
||||
- AzureBlobStorage
|
||||
- Hudi
|
||||
- MinMax
|
||||
- Bloom
|
||||
- N-gram
|
||||
- EmbeddedRocksDB
|
||||
- HDFS
|
||||
- Hive
|
||||
- ORC
|
||||
- Parquet
|
||||
- NATS
|
||||
- MySQL
|
||||
- ClickPipe
|
||||
- ClickPipes
|
||||
- Postgres
|
||||
- CDC
|
||||
- FAQ
|
||||
- Amazon Web Services
|
||||
- AWS
|
||||
- Frequently Asked Questions
|
||||
- PostgreSQL
|
||||
- TTL
|
||||
- ClickStack
|
||||
- OpenTelemetry
|
||||
- Filebeat
|
||||
- Elastic Agent
|
||||
- HyperDX
|
||||
- Helm
|
||||
- EDOT
|
||||
- SDK
|
||||
- Docker
|
||||
- Time To Live
|
||||
- Docker Compose
|
||||
- Kafka
|
||||
- Kafka Connect
|
||||
- Kafka Connect Sink
|
||||
- Google Cloud Run
|
||||
- NPM
|
||||
- OTel
|
||||
- SQL
|
||||
- OSS
|
||||
- URL
|
||||
- RabbitMQ
|
||||
- Iceberg
|
||||
- TimeSeries
|
||||
- AggregateFunction
|
||||
- SimpleAggregateFunction
|
||||
- FileLog
|
||||
- system.asynchronous_insert_log
|
||||
- system.asynchronous_loader
|
||||
- system.asynchronous_metrics
|
||||
- system.backups
|
||||
- system.backup_log
|
||||
- system.iceberg_history
|
||||
- system.latency_buckets
|
||||
- system.latency_log
|
||||
- system.histogram_metrics
|
||||
- system.licenses
|
||||
- system.merges
|
||||
- system.merge_tree_settings
|
||||
- system.metric_log
|
||||
- system.moves
|
||||
- system.numbers
|
||||
- system.numbers_mt
|
||||
- system.one
|
||||
- system.opentelemetry_span_log
|
||||
- system.mutations
|
||||
- system.parts_columns
|
||||
- system.projections
|
||||
- system.processors_profile_log
|
||||
- system.query_cache
|
||||
- system.query_condition_cache
|
||||
- system.query_metric_log
|
||||
- system.query_thread_log
|
||||
- system.quota_limits
|
||||
- system.query_views_log
|
||||
- system.quotas_usage
|
||||
- system.replicated_fetches
|
||||
- system.role_grants
|
||||
- system.replication_queue
|
||||
- system.s3_queue_settings
|
||||
- system.row_policies
|
||||
- system.schema_inference_cache
|
||||
- system.server_settings
|
||||
- system.settings_changes
|
||||
- system.session_log
|
||||
- system.settings_profile_elements
|
||||
- system.settings_profiles
|
||||
- system.stack_trace
|
||||
- system.storage_policies
|
||||
- system.table_engines
|
||||
- system.user_processes
|
||||
- system.view_refreshes
|
||||
- system.zookeeper_connection
|
||||
- system.zookeeper_log
|
||||
- AsynchronousHeavyMetricsCalculationTimeSpent
|
||||
- AsynchronousHeavyMetricsUpdateInterval
|
||||
- AsynchronousMetricsCalculationTimeSpent
|
||||
- AsynchronousMetricsUpdateInterval
|
||||
- BlockActiveTime_
|
||||
- BlockDiscardBytes_
|
||||
- BlockDiscardMerges_
|
||||
- BlockDiscardOps_
|
||||
- BlockDiscardTime_
|
||||
- BlockInFlightOps_
|
||||
- BlockQueueTime_
|
||||
- BlockReadBytes_
|
||||
- BlockReadMerges_
|
||||
- BlockReadOps_
|
||||
- BlockReadTime_
|
||||
- BlockWriteBytes_
|
||||
- BlockWriteMerges_
|
||||
- BlockWriteOps_
|
||||
- BlockWriteTime_
|
||||
- CPUFrequencyMHz_
|
||||
- AggregatorThreads
|
||||
- AggregatorThreadsActive
|
||||
- TablesLoaderForegroundThreads
|
||||
- TablesLoaderForegroundThreadsActive
|
||||
- TablesLoaderBackgroundThreads
|
||||
- TablesLoaderBackgroundThreadsActive
|
||||
- AsynchronousReadWait
|
||||
- BackgroundBufferFlushSchedulePoolSize
|
||||
- BackgroundBufferFlushSchedulePoolTask
|
||||
- BackgroundCommonPoolSize
|
||||
- BackgroundCommonPoolTask
|
||||
- BackgroundDistributedSchedulePoolSize
|
||||
- BackgroundDistributedSchedulePoolTask
|
||||
- BackgroundFetchesPoolSize
|
||||
- BackgroundFetchesPoolTask
|
||||
- BackgroundMergesAndMutationsPoolSize
|
||||
- BackgroundMergesAndMutationsPoolTask
|
||||
- BackgroundMessageBrokerSchedulePoolSize
|
||||
- BackgroundMessageBrokerSchedulePoolTask
|
||||
- BackgroundMovePoolSize
|
||||
- BackgroundMovePoolTask
|
||||
- BackgroundSchedulePoolSize
|
||||
- BackgroundSchedulePoolTask
|
||||
- CacheDetachedFileSegments
|
||||
- CacheDictionaryThreads
|
||||
- CacheDictionaryThreadsActive
|
||||
- CacheDictionaryUpdateQueueBatches
|
||||
- CacheFileSegments
|
||||
- ContextLockWait
|
||||
- DatabaseCatalogThreads
|
||||
- DatabaseCatalogThreadsActive
|
||||
- DatabaseOnDiskThreads
|
||||
- DatabaseOnDiskThreadsActive
|
||||
- DestroyAggregatesThreads
|
||||
- DestroyAggregatesThreadsActive
|
||||
- DictCacheRequests
|
||||
- DiskObjectStorageAsyncThreads
|
||||
- DiskObjectStorageAsyncThreadsActive
|
||||
- DiskSpaceReservedForMerge
|
||||
- DistributedSend
|
||||
- EphemeralNode
|
||||
- FilesystemCacheElements
|
||||
- FilesystemCacheReadBuffers
|
||||
- FilesystemCacheSize
|
||||
- QueryCacheBytes
|
||||
- QueryCacheEntries
|
||||
- UncompressedCacheBytes
|
||||
- UncompressedCacheCells
|
||||
- CompiledExpressionCacheBytes
|
||||
- CompiledExpressionCacheCount
|
||||
- MarkCacheBytes
|
||||
- MarkCacheFiles
|
||||
- GlobalThread
|
||||
- LocalThread
|
||||
- MemoryCode
|
||||
- MemoryShared
|
||||
- NetworkSend
|
||||
- OSGuestTime
|
||||
- OSNiceTime
|
||||
- OSOpenFiles
|
||||
- OSUptime
|
||||
- OSUserTime
|
||||
- PartMutation
|
||||
- PartsActive
|
||||
- PartsCompact
|
||||
- PartsWide
|
||||
- QueryThread
|
||||
- RemoteRead
|
||||
- SendScalars
|
||||
- keeper_response_time_ms_bucket
|
||||
- KEY_COLUMN_USAGE
|
||||
- user_name/password
|
||||
- username/ssh-key
|
||||
- access_management
|
||||
- grants
|
||||
- user_name/networks
|
||||
- user_name/profile
|
||||
- user_name/quota
|
||||
- user_name/databases
|
||||
- readonly
|
||||
- allow_ddl
|
||||
- max_pipeline_depth
|
||||
- histogram_metrics
|
||||
- latency_log
|
||||
- system.part_log
|
||||
- system.parts
|
||||
- system.processes
|
||||
- system.query_log
|
||||
- backup_log
|
||||
- backups
|
||||
- clickhouse-benchmark
|
||||
- clickhouse-format utility
|
||||
- compression
|
||||
- core_dump
|
||||
- crash_log
|
||||
- encryption
|
||||
- error_log
|
||||
- Example
|
||||
- File System
|
||||
- graphite
|
||||
- hsts_max_age
|
||||
- Huge Pages
|
||||
- include_from
|
||||
- ldap_servers
|
||||
- Linux Kernel
|
||||
- listen_host
|
||||
- listen_try
|
||||
- logger
|
||||
- macros
|
||||
- merge_tree
|
||||
- metric_log
|
||||
- mysql_port
|
||||
- openSSL
|
||||
- part_log
|
||||
- path
|
||||
- prometheus
|
||||
- proxy
|
||||
- query_cache
|
||||
- query_log
|
||||
- s3queue_log
|
||||
- ssh_server
|
||||
- system.metrics
|
||||
- system.quotas
|
||||
- system.replicas
|
||||
- system.resources
|
||||
- system.roles
|
||||
- system.scheduler
|
||||
- system.settings
|
||||
- system.table_engine
|
||||
- system.tables
|
||||
- system.text_log
|
||||
- system.time_zones
|
||||
- system.trace_log
|
||||
- system.users
|
||||
- system.warnings
|
||||
- system.workloads
|
||||
- system.zookeeper
|
||||
- tcp_port
|
||||
- tcp_ssh_port
|
||||
- text_log
|
||||
- timezone
|
||||
- trace_log
|
||||
- users_config
|
||||
- zookeeper
|
||||
- clickhouse_backupview
|
||||
- jemalloc.active
|
||||
- jemalloc.allocated
|
||||
- jemalloc.arenas.all.dirty_purged
|
||||
- jemalloc.arenas.all.muzzy_purged
|
||||
- jemalloc.arenas.all.pactive
|
||||
- jemalloc.arenas.all.pdirty
|
||||
- jemalloc.arenas.all.pmuzzy
|
||||
- jemalloc.background_thread.num_runs
|
||||
- jemalloc.background_thread.num_threads
|
||||
- jemalloc.background_thread.run_intervals
|
||||
- jemalloc.epoch
|
||||
- jemalloc.mapped
|
||||
- jemalloc.metadata
|
||||
- jemalloc.metadata_thp
|
||||
- jemalloc.resident
|
||||
- jemalloc.retained
|
||||
- jemalloc.prof.active
|
||||
- clickhouse-keeper-client
|
||||
- toBFloat16
|
||||
- toFloat32
|
||||
- toFloat64
|
||||
- toDecimal32
|
||||
- toDecimal64
|
||||
- toDecimal128
|
||||
- toDecimal256
|
||||
- toFloat32OrZero
|
||||
- toFloat32OrNull
|
||||
- toFloat32OrDefault
|
||||
- toFloat64OrZero
|
||||
- toFloat64OrNull
|
||||
- toFloat64OrDefault
|
||||
- toBFloat16OrZero
|
||||
- toBFloat16OrNull
|
||||
- toDecimal32OrZero
|
||||
- toDecimal32OrNull
|
||||
- toDecimal32OrDefault
|
||||
- toDecimal64OrZero
|
||||
- toDecimal64OrNull
|
||||
- toDecimal64OrDefault
|
||||
- toDecimal128OrZero
|
||||
- toDecimal128OrNull
|
||||
- toDecimal128OrDefault
|
||||
- toDecimal256OrZero
|
||||
- toDecimal256OrNull
|
||||
- toDecimal256OrDefault
|
||||
- reinterpretAsFloat32
|
||||
- reinterpretAsFloat64
|
||||
- accurateCast
|
||||
- accurateCastOrNull
|
||||
- accurateCastOrDefault
|
||||
- toUnixTimestamp64Second
|
||||
- toUnixTimestamp64Milli
|
||||
- toUnixTimestamp64Micro
|
||||
- toUnixTimestamp64Nano
|
||||
- fromUnixTimestamp64Second
|
||||
- fromUnixTimestamp64Milli
|
||||
- fromUnixTimestamp64Micro
|
||||
- fromUnixTimestamp64Nano
|
||||
- accurateCast.*
|
||||
- accurateCastOrNull.*
|
||||
- accurateCastOrDefault.*
|
||||
- fromDaysSinceYearZero32
|
||||
- now64
|
||||
- getMacro
|
||||
- blockSize
|
||||
- currentUser
|
||||
- transform.*
|
||||
- runningDifference
|
||||
- lengthUTF8
|
||||
- leftUTF8
|
||||
- leftPadUTF8
|
||||
- rightUTF8
|
||||
- rightPadUTF8
|
||||
- lowerUTF8
|
||||
- upperUTF8
|
||||
- isValidUTF8
|
||||
- toValidUTF8
|
||||
- reverseUTF8
|
||||
- substringUTF8
|
||||
- endsWithUTF8
|
||||
- startsWithUTF8
|
||||
- normalizeUTF8NFC
|
||||
- normalizeUTF8NFD
|
||||
- normalizeUTF8NFKC
|
||||
- normalizeUTF8NFKD
|
||||
- editDistanceUTF8
|
||||
- initcapUTF8
|
||||
- sparseGramsUTF8
|
||||
- sparseGramsHashesUTF8
|
||||
- base32Encode
|
||||
- base32Decode
|
||||
- tryBase32Decode
|
||||
- base58Encode
|
||||
- base58Decode
|
||||
- tryBase58Decode
|
||||
- base64Encode
|
||||
- base64Decode
|
||||
- tryBase64Decode
|
||||
- CRC32
|
||||
- CRC64
|
||||
- positionUTF8
|
||||
- multiSearchAllPositionsUTF8
|
||||
- multiSearchFirstPositionUTF8
|
||||
- multiSearchAnyUTF8
|
||||
- ngramDistanceUTF8
|
||||
- ngramSearchUTF8
|
||||
- hasSubsequenceUTF8
|
||||
- halfMD5
|
||||
- MD4
|
||||
- MD5
|
||||
- sipHash64
|
||||
- sipHash64Keyed
|
||||
- sipHash128
|
||||
- sipHash128Keyed
|
||||
- sipHash128Reference
|
||||
- sipHash128ReferenceKeyed
|
||||
- cityHash64
|
||||
- intHash32
|
||||
- intHash64
|
||||
- SHA1
|
||||
- SHA224
|
||||
- SHA256
|
||||
- SHA512
|
||||
- SHA512_256
|
||||
- SHA1, SHA224, SHA256, SHA512, SHA512_256
|
||||
- BLAKE3
|
||||
- farmFingerprint64
|
||||
- farmHash64
|
||||
- javaHashUTF16LE
|
||||
- metroHash64
|
||||
- murmurHash2_32
|
||||
- murmurHash2_64
|
||||
- murmurHash3_32
|
||||
- murmurHash3_64
|
||||
- murmurHash3_128
|
||||
- xxh3
|
||||
- xxHash32
|
||||
- xxHash64
|
||||
- ngramSimHashUTF8
|
||||
- wordShingleSimHashUTF8
|
||||
- wyHash64
|
||||
- ngramMinHashUTF8
|
||||
- ngramMinHashArgUTF8
|
||||
- wordShingleMinHashUTF8
|
||||
- wordShingleMinHashArgUTF8
|
||||
- keccak256
|
||||
- uniqUpTo.*
|
||||
- quantileBFloat16Weighted
|
||||
- quantiles
|
||||
- quantileExact
|
||||
- uniqHLL12
|
||||
- uniqCombined64
|
||||
- nonNegativeDerivative.*
|
||||
- MySQL Docs
|
||||
- Postgres Docs
|
||||
- simpleJSON.*functions
|
||||
- atan2
|
||||
- e
|
||||
- exp10
|
||||
- exp2
|
||||
- intExp10
|
||||
- intExp2
|
||||
- log10
|
||||
- log1p
|
||||
- log2
|
||||
- bitmaskToList.*
|
||||
- bitmaskToArray.*
|
||||
- bitPositionsToArray.*
|
||||
- h3kRing
|
||||
- stringToH3
|
||||
- h3PointDistM
|
||||
- h3PointDistKm
|
||||
- h3PointDistRads
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Spell out all ordinal numbers ('%s') in text."
|
||||
link: 'https://developers.google.com/style/numbers'
|
||||
level: warning
|
||||
nonword: true
|
||||
tokens:
|
||||
- (?<!(?:January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+)\d+(?:st|nd|rd|th)(?!,?\s*\d{4})
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
---
|
||||
name: gitlab_base.OxfordComma
|
||||
description: |
|
||||
Checks for the lack of an Oxford comma. In some cases, will catch overly
|
||||
complex sentence structures with lots of commas.
|
||||
extends: existence
|
||||
message: "Use a comma before the last 'and' or 'or' in a list of four or more items."
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/#punctuation
|
||||
vocab: false
|
||||
level: warning
|
||||
raw:
|
||||
- '(?:[\w-_` ]+,){2,}(?:[\w-_` ]+) (and |or )'
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Commas and periods go inside quotation marks."
|
||||
link: 'https://developers.google.com/style/quotation-marks'
|
||||
level: warning
|
||||
nonword: true
|
||||
tokens:
|
||||
- '"[^"]+"[.,?]'
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.Repetition
|
||||
description: |
|
||||
Checks for duplicate words, like `the the` or `and and`.
|
||||
extends: repetition
|
||||
message: "Remove this duplicate word: '%s'."
|
||||
vocab: false
|
||||
level: warning
|
||||
alpha: true
|
||||
tokens:
|
||||
- '[^\s]+'
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.SentenceLength
|
||||
description: |
|
||||
Counts words in a sentence and alerts if a sentence exceeds 25 words.
|
||||
extends: occurrence
|
||||
message: "Improve readability by using fewer than 25 words in this sentence."
|
||||
scope: sentence
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/#language
|
||||
level: suggestion
|
||||
max: 25
|
||||
token: \b(\w+)\b
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.Units
|
||||
description: |
|
||||
Recommends a space between a number and a unit of measure.
|
||||
extends: existence
|
||||
message: "Add a space between the number and the unit in '%s'."
|
||||
link: 'https://docs.gitlab.com/development/documentation/styleguide/'
|
||||
vocab: false
|
||||
nonword: true
|
||||
level: warning
|
||||
ignorecase: true
|
||||
tokens:
|
||||
- \d+(?:B|kB|KiB|MB|MiB|GB|GiB|TB|TiB)
|
||||
- \d+(?:ns|ms|μs|s|min|h|d)\b
|
||||
@@ -1,278 +0,0 @@
|
||||
---
|
||||
name: gitlab_base.Uppercase
|
||||
description: |
|
||||
Checks for use of all uppercase letters with unknown reason.
|
||||
extends: conditional
|
||||
message: "Instead of uppercase for '%s', use lowercase or backticks (`) if possible. Otherwise, ask a Technical Writer to add this word or acronym to the rule's exception list."
|
||||
link: https://docs.gitlab.com/development/documentation/testing/vale/#uppercase-acronym-test
|
||||
vocab: false
|
||||
level: suggestion
|
||||
ignorecase: false
|
||||
# Ensures that the existence of 'first' implies the existence of 'second'.
|
||||
first: '\b([A-Z]{3,5})\b'
|
||||
second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
|
||||
# ... with the exception of these:
|
||||
exceptions:
|
||||
- ACH
|
||||
- ACL
|
||||
- AJAX
|
||||
- ALL
|
||||
- AMI
|
||||
- ANSI
|
||||
- APAC
|
||||
- API
|
||||
- APM
|
||||
- ARIA
|
||||
- ARM
|
||||
- ARN
|
||||
- ASCII
|
||||
- ASG
|
||||
- AST
|
||||
- AWS
|
||||
- BETA
|
||||
- BMP
|
||||
- BSD
|
||||
- CAS
|
||||
- CDN
|
||||
- CGI
|
||||
- CIDR
|
||||
- CLI
|
||||
- CNA
|
||||
- CNCF
|
||||
- CORE
|
||||
- CORS
|
||||
- CPU
|
||||
- CRAN
|
||||
- CRIME
|
||||
- CRM
|
||||
- CRUD
|
||||
- CSRF
|
||||
- CSS
|
||||
- CSV
|
||||
- CTE
|
||||
- CVE
|
||||
- CVS
|
||||
- CVSS
|
||||
- CWE
|
||||
- DAST
|
||||
- DDL
|
||||
- DHCP
|
||||
- DML
|
||||
- DNS
|
||||
- DOM
|
||||
- DORA
|
||||
- DSA
|
||||
- DSL
|
||||
- DSN
|
||||
- DUOENT
|
||||
- DUOPRO
|
||||
- DVCS
|
||||
- DVD
|
||||
- EBS
|
||||
- ECDSA
|
||||
- ECS
|
||||
- EFS
|
||||
- EKS
|
||||
- ELB
|
||||
- ENA
|
||||
- EOL
|
||||
- EWM
|
||||
- EXIF
|
||||
- FAQ
|
||||
- FIDO
|
||||
- FIFO
|
||||
- FIPS
|
||||
- FLAG
|
||||
- FOSS
|
||||
- FQDN
|
||||
- FREE
|
||||
- FTP
|
||||
- GCP
|
||||
- GDK
|
||||
- GDPR
|
||||
- GET
|
||||
- GID
|
||||
- GIF
|
||||
- GKE
|
||||
- GLEX
|
||||
- GLFM
|
||||
- GNU
|
||||
- GPG
|
||||
- GPL
|
||||
- GPS
|
||||
- GPT
|
||||
- GPU
|
||||
- GUI
|
||||
- HAML
|
||||
- HAR
|
||||
- HDD
|
||||
- HEAD
|
||||
- HIPAA
|
||||
- HLL
|
||||
- HSTS
|
||||
- HTML
|
||||
- HTTP
|
||||
- HTTPS
|
||||
- IAM
|
||||
- IANA
|
||||
- IBM
|
||||
- ICO
|
||||
- IDE
|
||||
- IID
|
||||
- IIS
|
||||
- IMAP
|
||||
- IOPS
|
||||
- IRC
|
||||
- ISO
|
||||
- JPEG
|
||||
- JPG
|
||||
- JSON
|
||||
- JSONB
|
||||
- JVM
|
||||
- JWT
|
||||
- KICS
|
||||
- LAN
|
||||
- LDAP
|
||||
- LDAPS
|
||||
- LESS
|
||||
- LFS
|
||||
- LLM
|
||||
- LRU
|
||||
- LSIF
|
||||
- LTM
|
||||
- LTS
|
||||
- LTSS
|
||||
- LVM
|
||||
- MIME
|
||||
- MIT
|
||||
- MITRE
|
||||
- MVC
|
||||
- NAS
|
||||
- NAT
|
||||
- NDA
|
||||
- NFS
|
||||
- NGINX
|
||||
- NOTE
|
||||
- NPM
|
||||
- NTP
|
||||
- OCI
|
||||
- OIDC
|
||||
- OKD
|
||||
- OKR
|
||||
- ONLY
|
||||
- OSS
|
||||
- OTP
|
||||
- OWASP
|
||||
- PAT
|
||||
- PAYG
|
||||
- PCI-DSS
|
||||
- PDF
|
||||
- PEM
|
||||
- PEP
|
||||
- PGP
|
||||
- PHP
|
||||
- PID
|
||||
- PKCS
|
||||
- PMD
|
||||
- PNG
|
||||
- POC
|
||||
- POSIX
|
||||
- POST
|
||||
- PROXY
|
||||
- PUT
|
||||
- QPS
|
||||
- RAID
|
||||
- RAM
|
||||
- RBAC
|
||||
- RDP
|
||||
- RDS
|
||||
- REST
|
||||
- RFC
|
||||
- RHEL
|
||||
- RPC
|
||||
- RPM
|
||||
- RPO
|
||||
- RPS
|
||||
- RSA
|
||||
- RSS
|
||||
- RTC
|
||||
- RTO
|
||||
- RVM
|
||||
- SAAS
|
||||
- SAML
|
||||
- SAN
|
||||
- SAST
|
||||
- SATA
|
||||
- SBOM
|
||||
- SBT
|
||||
- SCIM
|
||||
- SCM
|
||||
- SCP
|
||||
- SCSS
|
||||
- SDK
|
||||
- SELF
|
||||
- SEO
|
||||
- SES
|
||||
- SFTP
|
||||
- SHA
|
||||
- SKI
|
||||
- SLA
|
||||
- SLI
|
||||
- SLO
|
||||
- SLSA
|
||||
- SMS
|
||||
- SMTP
|
||||
- SOAP
|
||||
- SOC
|
||||
- SOX
|
||||
- SPDX
|
||||
- SPDY
|
||||
- SPF
|
||||
- SQL
|
||||
- SRE
|
||||
- SSD
|
||||
- SSF
|
||||
- SSG
|
||||
- SSH
|
||||
- SSL
|
||||
- SSO
|
||||
- STI
|
||||
- SUSE
|
||||
- SVG
|
||||
- SVN
|
||||
- TCP
|
||||
- TIFF
|
||||
- TIP
|
||||
- TLD
|
||||
- TLS
|
||||
- TODO
|
||||
- TOML
|
||||
- TOTP
|
||||
- TPS
|
||||
- TTL
|
||||
- UBI
|
||||
- UDP
|
||||
- UID
|
||||
- UNIX
|
||||
- URI
|
||||
- URL
|
||||
- USB
|
||||
- USD
|
||||
- UTC
|
||||
- UTF
|
||||
- UUID
|
||||
- VCS
|
||||
- VPC
|
||||
- VPN
|
||||
- WAF
|
||||
- WEBP
|
||||
- WIP
|
||||
- WSL
|
||||
- XML
|
||||
- XSS
|
||||
- YAML
|
||||
- ZAP
|
||||
- ZIP
|
||||
- GCS
|
||||
- BYOC
|
||||
- CDC
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
---
|
||||
name: gitlab_base.Wordy
|
||||
description: |
|
||||
Suggests shorter versions of wordy phrases.
|
||||
extends: substitution
|
||||
message: "%s"
|
||||
link: https://docs.gitlab.com/development/documentation/styleguide/word_list/
|
||||
vocab: false
|
||||
level: suggestion
|
||||
ignorecase: true
|
||||
swap:
|
||||
a number of: "Specify the number or remove the phrase."
|
||||
as well as: "Use 'and' instead of 'as well as'."
|
||||
note that: "Remove the phrase 'note that'."
|
||||
please: "Use 'please' only if we've inconvenienced the user."
|
||||
respectively: "Remove 'respectively' and list each option instead."
|
||||
and so on: "Remove 'and so on'. Try to use 'like' and provide examples instead."
|
||||
in order to: "Remove 'in order' and leave 'to'."
|
||||
quite: "Remove 'quite', as it's wordy."
|
||||
+44
-46
@@ -1,78 +1,76 @@
|
||||
/**
|
||||
* pkg.hanzo.ai — Cloudflare Worker
|
||||
*
|
||||
* Proxies and rebrands upstream datastore packages.
|
||||
* Routes:
|
||||
* /datastore/tgz/{channel}/{package}-{version}-{arch}.tgz
|
||||
* → packages.clickhouse.com/tgz/{channel}/clickhouse-{component}-{version}-{arch}.tgz
|
||||
* Serves Hanzo Datastore packages from our own artifact store.
|
||||
*
|
||||
* /datastore/tgz/{channel}/{package}-{version}-{arch}.tgz
|
||||
* /datastore/deb/pool/main/{package}-{version}_{arch}.deb
|
||||
* → packages.clickhouse.com/deb/pool/main/c/clickhouse/clickhouse-{component}-{version}_{arch}.deb
|
||||
*
|
||||
* It used to fetch these from upstream's package host and rename the file on the
|
||||
* way through, so `datastore-server` resolved to an upstream binary served under
|
||||
* our host with our `x-served-by`. That is worse than serving nothing: it hands
|
||||
* someone another project's build believing it is ours, and it makes our package
|
||||
* channel a live dependency on a third party's release schedule and uptime.
|
||||
*
|
||||
* So the upstream base and the name map are gone. This serves what WE build, and
|
||||
* says so plainly when we have not built it yet — a 503 naming the artifact that
|
||||
* does exist beats a 200 carrying the wrong bytes.
|
||||
*
|
||||
* WHAT WE PUBLISH TODAY: the container image, `ghcr.io/hanzoai/datastore` (see
|
||||
* hanzo.yml). There is no apt/tgz lane yet — hanzo.yml says so where it explains
|
||||
* why the image builds from source rather than from the .deb path. When that lane
|
||||
* lands, point PKG_BASE at it and these routes start answering.
|
||||
*/
|
||||
|
||||
const UPSTREAM_BASE = 'https://packages.clickhouse.com';
|
||||
/// Where our own built packages live, once a lane publishes them. Set as a
|
||||
/// Worker environment variable rather than baked in, so standing one up does not
|
||||
/// need a code change. Empty means "we do not publish these yet", which is the
|
||||
/// honest state today.
|
||||
const PKG_BASE_VAR = 'PKG_BASE';
|
||||
|
||||
// Map our package names to upstream
|
||||
const PACKAGE_MAP = {
|
||||
'datastore-client': 'clickhouse-client',
|
||||
'datastore-server': 'clickhouse-server',
|
||||
'datastore-common-static': 'clickhouse-common-static',
|
||||
'datastore-keeper': 'clickhouse-keeper',
|
||||
};
|
||||
/// What we DO publish, named in the 503 so the reader has somewhere to go.
|
||||
const IMAGE = 'ghcr.io/hanzoai/datastore';
|
||||
|
||||
export default {
|
||||
async fetch(request) {
|
||||
async fetch(request, env) {
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
|
||||
// Health check
|
||||
if (path === '/ping' || path === '/') {
|
||||
return new Response('pkg.hanzo.ai\n', {
|
||||
headers: { 'content-type': 'text/plain' },
|
||||
});
|
||||
}
|
||||
|
||||
// TGZ packages: /datastore/tgz/{channel}/{package}-{version}-{arch}.tgz
|
||||
const tgzMatch = path.match(/^\/datastore\/tgz\/(.+)$/);
|
||||
if (tgzMatch) {
|
||||
let subpath = tgzMatch[1];
|
||||
// Replace our package names with upstream
|
||||
for (const [ours, upstream] of Object.entries(PACKAGE_MAP)) {
|
||||
subpath = subpath.replace(ours, upstream);
|
||||
}
|
||||
const upstreamUrl = `${UPSTREAM_BASE}/tgz/${subpath}`;
|
||||
return proxyUpstream(upstreamUrl, request);
|
||||
const match = path.match(/^\/datastore\/(tgz|deb)\/(.+)$/);
|
||||
if (!match) {
|
||||
return new Response('Not Found\n', { status: 404 });
|
||||
}
|
||||
|
||||
// DEB packages: /datastore/deb/pool/main/{package}...
|
||||
const debMatch = path.match(/^\/datastore\/deb\/(.+)$/);
|
||||
if (debMatch) {
|
||||
let subpath = debMatch[1];
|
||||
// Replace our package names with upstream
|
||||
for (const [ours, upstream] of Object.entries(PACKAGE_MAP)) {
|
||||
subpath = subpath.replace(ours, upstream);
|
||||
}
|
||||
// Fix the path structure for deb (upstream uses /c/clickhouse/ prefix)
|
||||
if (!subpath.includes('/c/clickhouse/')) {
|
||||
subpath = subpath.replace('pool/main/', 'pool/main/c/clickhouse/');
|
||||
}
|
||||
const upstreamUrl = `${UPSTREAM_BASE}/deb/${subpath}`;
|
||||
return proxyUpstream(upstreamUrl, request);
|
||||
const base = (env && env[PKG_BASE_VAR]) || '';
|
||||
if (!base) {
|
||||
return new Response(
|
||||
`No package lane yet.\n\n` +
|
||||
`Hanzo Datastore publishes a container image and not (yet) apt or tgz\n` +
|
||||
`packages, so there is nothing correct to serve here:\n\n` +
|
||||
` docker pull ${IMAGE}\n\n` +
|
||||
`This endpoint used to proxy another project's packages under our name.\n` +
|
||||
`It does not any more.\n`,
|
||||
{ status: 503, headers: { 'content-type': 'text/plain' } },
|
||||
);
|
||||
}
|
||||
|
||||
return new Response('Not Found\n', { status: 404 });
|
||||
const [, kind, subpath] = match;
|
||||
return serve(`${base.replace(/\/+$/, '')}/${kind}/${subpath}`, request);
|
||||
},
|
||||
};
|
||||
|
||||
async function proxyUpstream(upstreamUrl, request) {
|
||||
const resp = await fetch(upstreamUrl, {
|
||||
async function serve(target, request) {
|
||||
const resp = await fetch(target, {
|
||||
method: request.method,
|
||||
headers: {
|
||||
'User-Agent': 'pkg.hanzo.ai/1.0',
|
||||
},
|
||||
headers: { 'User-Agent': 'pkg.hanzo.ai/1.0' },
|
||||
});
|
||||
|
||||
// Clone response with our headers
|
||||
const headers = new Headers(resp.headers);
|
||||
headers.set('x-served-by', 'pkg.hanzo.ai');
|
||||
headers.delete('server');
|
||||
|
||||
Reference in New Issue
Block a user