Update pipeline to Node 24. Update Lerna. Update Vitepress to Beta. Replace...

This commit is contained in:
2026-08-08 11:06:49 +00:00
committed by Emergent Reasons
parent 53b7fb3aef
commit 1c3e920c85
11 changed files with 4456 additions and 5950 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
image: node:22 image: node:24
# We use stages for clarity of intent, and "needs" for dependencies and sequencing. # We use stages for clarity of intent, and "needs" for dependencies and sequencing.
# "needs" should work in parallel where acceptable, and get any artifacts directly from the source step. # "needs" should work in parallel where acceptable, and get any artifacts directly from the source step.
+4173 -5725
View File
File diff suppressed because it is too large Load Diff
+10 -9
View File
@@ -54,25 +54,26 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.10.0", "@eslint/js": "^9.10.0",
"@types/eslint__js": "^8.42.3", "@types/eslint__js": "^8.42.3",
"@typescript-eslint/eslint-plugin": "^8.5.0", "@typescript-eslint/eslint-plugin": "^8.66.0",
"@typescript-eslint/parser": "^8.5.0", "@typescript-eslint/parser": "^8.66.0",
"@vitest/coverage-v8": "^3.2.4", "@vitest/coverage-v8": "^3.2.4",
"eslint": "^9.10.0", "eslint": "^9.10.0",
"eslint-config-prettier": "^8.10.0", "eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-prettier": "^5.2.1",
"lerna": "^9.0.3", "lerna": "^10.0.0",
"madge": "^8.0.0", "madge": "^8.0.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"typescript": "5.7.2", "typescript": "5.9.3",
"typescript-eslint": "^8.6.0", "typescript-eslint": "^8.66.0",
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"overrides": { "overrides": {
"vitepress": { "nx": {
"vite": "^7.1.10" "brace-expansion": "5.0.9"
}, },
"vitepress-plugin-mermaid": { "lerna": {
"vite": "^7.1.10" "js-yaml": "4.3.1",
"tar": "7.5.22"
} }
} }
} }
+3 -3
View File
@@ -17,13 +17,13 @@
"@monaco-editor/loader": "^1.5.0", "@monaco-editor/loader": "^1.5.0",
"@xocash/primitives": "*", "@xocash/primitives": "*",
"@xocash/stack": "*", "@xocash/stack": "*",
"monaco-editor": "^0.52.2" "monaco-editor": "^0.52.2",
"vitepress-mermaid-renderer": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"typedoc": "^0.28.14", "typedoc": "^0.28.14",
"typedoc-plugin-markdown": "^4.3.2", "typedoc-plugin-markdown": "^4.3.2",
"typedoc-vitepress-theme": "^1.1.1", "typedoc-vitepress-theme": "^1.1.1",
"vitepress": "^1.6.4", "vitepress": "2.0.0-alpha.18"
"vitepress-plugin-mermaid": "^2.0.17"
} }
} }
+189 -192
View File
@@ -1,5 +1,4 @@
import { defineConfig } from 'vitepress'; import { defineConfig } from 'vitepress';
import { withMermaid } from 'vitepress-plugin-mermaid';
import typedocSidebar from '../api/typedoc-sidebar.json'; import typedocSidebar from '../api/typedoc-sidebar.json';
// Node imports for auto blog sidebar (works at docs/src/.vitepress) // Node imports for auto blog sidebar (works at docs/src/.vitepress)
@@ -39,8 +38,8 @@ function toPascalCase(str: string) {
.map((word) => { .map((word) => {
if (word.length === 0) return ''; if (word.length === 0) return '';
if (SPECIAL_CASES[word]) { if (word in SPECIAL_CASES) {
return SPECIAL_CASES[word]; return SPECIAL_CASES[word as keyof typeof SPECIAL_CASES];
} }
// Capitalize first letter, lowercase rest // Capitalize first letter, lowercase rest
@@ -114,210 +113,208 @@ function blogSidebar() {
} }
// https://vitepress.dev/reference/site-config // https://vitepress.dev/reference/site-config
export default withMermaid( export default defineConfig({
defineConfig({ outDir: '../dist',
outDir: '../dist', title: 'XO Stack',
title: 'XO Stack', description:
description: 'XO Stack: Building smart applications on the Bitcoin Cash Network',
'XO Stack: Building smart applications on the Bitcoin Cash Network', head: [
head: [ // Favicon.
// Favicon. ['link', { rel: 'icon', href: '/favicon.png' }],
['link', { rel: 'icon', href: '/favicon.png' }], // Basic OpenGraph tags
// Basic OpenGraph tags ['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:type', content: 'website' }], ['meta', { property: 'og:title', content: 'XO Stack' }],
['meta', { property: 'og:title', content: 'XO Stack' }], [
[ 'meta',
'meta', {
{ property: 'og:description',
property: 'og:description', content:
content: 'XO Stack: Building smart applications on the Bitcoin Cash Network',
'XO Stack: Building smart applications on the Bitcoin Cash Network',
},
],
[
'meta',
{
property: 'og:image',
content: 'https://stack.xo.cash/stack-opengraph.jpg',
},
],
['meta', { property: 'og:url', content: 'https://stack.xo.cash/' }],
// Optional additional tags
['meta', { property: 'og:site_name', content: 'XO Stack' }],
// Twitter Card tags (optional but recommended)
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
['meta', { name: 'twitter:site', content: '@yourtwitterhandle' }],
],
themeConfig: {
// Set the logo
logo: {
light: '/logo-light.svg',
dark: '/logo-dark.svg',
}, },
],
[
'meta',
{
property: 'og:image',
content: 'https://stack.xo.cash/stack-opengraph.jpg',
},
],
['meta', { property: 'og:url', content: 'https://stack.xo.cash/' }],
// Enable search functionality. // Optional additional tags
// TODO: Re-enable once we release documentation. ['meta', { property: 'og:site_name', content: 'XO Stack' }],
/*
// Twitter Card tags (optional but recommended)
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
['meta', { name: 'twitter:site', content: '@yourtwitterhandle' }],
],
themeConfig: {
// Set the logo
logo: {
light: '/logo-light.svg',
dark: '/logo-dark.svg',
},
// Enable search functionality.
// TODO: Re-enable once we release documentation.
/*
search: { search: {
provider: 'local', provider: 'local',
}, },
*/ */
// https://vitepress.dev/reference/default-theme-config // https://vitepress.dev/reference/default-theme-config
nav: [ nav: [
{ text: 'Home', link: '/' }, { text: 'Home', link: '/' },
// TODO: Re-enable once we release documentation // TODO: Re-enable once we release documentation
// { text: 'Documentation', link: '/quick-start' }, // { text: 'Documentation', link: '/quick-start' },
{ text: 'Blog', link: '/blog' }, { text: 'Blog', link: '/blog' },
{ text: 'Tools', link: '/tools/primitives' }, { text: 'Tools', link: '/tools/primitives' },
{ text: 'Playground', link: '/playground' }, { text: 'Playground', link: '/playground' },
],
sidebar: {
// Default Side Menu.
'/': [
{
text: 'Guide',
items: [
{ text: 'Quick Start', link: '/quick-start' },
{ text: 'Architecture', link: '/architecture' },
{ text: 'Best Practices', link: '/api-examples' },
{ text: 'Contributing', link: '/contributing' },
],
},
{
text: 'Frameworks',
items: [
{ text: 'Vue', link: '/frameworks/vue' },
{ text: 'React', link: '/frameworks/react' },
{ text: 'Svelte', link: '/frameworks/svelte' },
],
},
{
text: 'API',
items: convertNavToPascalCase(typedocSidebar),
},
], ],
// Side-menu for all routes under /tools/
sidebar: { '/tools/': [
// Default Side Menu. {
'/': [ text: 'XO Tools',
{ items: [
text: 'Guide', {
items: [ text: 'Primitives',
{ text: 'Quick Start', link: '/quick-start' }, link: '/tools/primitives',
{ text: 'Architecture', link: '/architecture' }, collapsed: false,
{ text: 'Best Practices', link: '/api-examples' }, items: [
{ text: 'Contributing', link: '/contributing' }, { text: 'Address', link: '/tools/primitives#address' },
], {
}, text: 'Block Header',
{ link: '/tools/primitives#block-header',
text: 'Frameworks', },
items: [ { text: 'Bytes', link: '/tools/primitives#bytes' },
{ text: 'Vue', link: '/frameworks/vue' }, {
{ text: 'React', link: '/frameworks/react' }, text: 'Encrypter',
{ text: 'Svelte', link: '/frameworks/svelte' }, link: '/tools/primitives#encrypter',
], },
}, {
{ text: 'Hasher',
text: 'API', link: '/tools/primitives#hasher',
items: convertNavToPascalCase(typedocSidebar), },
}, {
], text: 'HD Private Node',
// Side-menu for all routes under /tools/ link: '/tools/primitives#hd-private-node',
'/tools/': [ },
{ {
text: 'XO Tools', text: 'HD Public Node',
items: [ link: '/tools/primitives#hd-public-node',
{ },
text: 'Primitives', {
link: '/tools/primitives', text: 'Mnemonic',
collapsed: false, link: '/tools/primitives#mnemonic',
items: [ },
{ text: 'Address', link: '/tools/primitives#address' }, {
{ text: 'Private Key',
text: 'Block Header', link: '/tools/primitives#private-key',
link: '/tools/primitives#block-header', },
}, {
{ text: 'Bytes', link: '/tools/primitives#bytes' }, text: 'Public Key',
{ link: '/tools/primitives#public-key',
text: 'Encrypter', },
link: '/tools/primitives#encrypter', {
}, text: 'Satoshis',
{ link: '/tools/primitives#satoshis',
text: 'Hasher', },
link: '/tools/primitives#hasher', {
}, text: 'Script',
{ link: '/tools/primitives#script',
text: 'HD Private Node', },
link: '/tools/primitives#hd-private-node', {
}, text: 'Transaction',
{ link: '/tools/primitives#transaction',
text: 'HD Public Node', },
link: '/tools/primitives#hd-public-node', ],
}, },
{ {
text: 'Mnemonic', text: 'Stack',
link: '/tools/primitives#mnemonic', collapsed: false,
}, items: [
{ {
text: 'Private Key', text: 'Blockchain',
link: '/tools/primitives#private-key', link: '/tools/stack/blockchain',
}, items: [
{ {
text: 'Public Key', text: 'Electrum',
link: '/tools/primitives#public-key', link: '/tools/stack/blockchain#electrum',
}, },
{ ],
text: 'Satoshis', },
link: '/tools/primitives#satoshis', ],
}, },
{ ],
text: 'Script', },
link: '/tools/primitives#script',
},
{
text: 'Transaction',
link: '/tools/primitives#transaction',
},
],
},
{
text: 'Stack',
collapsed: false,
items: [
{
text: 'Blockchain',
link: '/tools/stack/blockchain',
items: [
{
text: 'Electrum',
link: '/tools/stack/blockchain#electrum',
},
],
},
],
},
],
},
],
// Blog sidebar (auto-generated)
'/blog/': blogSidebar(),
},
socialLinks: [
// { icon: 'github', link: 'https://gitlab.com/GeneralProtocols/xo/stack' },
{ icon: 'x', link: 'https://x.com/xo_bitcoincash' },
], ],
// Blog sidebar (auto-generated)
// This is the outline on the right-hand side of the page. '/blog/': blogSidebar(),
outline: {
level: [2, 4],
label: 'On this page',
},
}, },
// Set the dev and build targets to "esnext" socialLinks: [
// NOTE: We do this so that we can support top-level-await. // { icon: 'github', link: 'https://gitlab.com/GeneralProtocols/xo/stack' },
vite: { { icon: 'x', link: 'https://x.com/xo_bitcoincash' },
esbuild: { ],
// This is the outline on the right-hand side of the page.
outline: {
level: [2, 4],
label: 'On this page',
},
},
// Set the dev and build targets to "esnext"
// NOTE: We do this so that we can support top-level-await.
vite: {
esbuild: {
target: 'esnext',
},
build: {
target: 'esnext',
},
optimizeDeps: {
esbuildOptions: {
target: 'esnext', target: 'esnext',
}, },
build: { },
target: 'esnext', resolve: {
}, // Configure to use browser version of @xocash/stack
optimizeDeps: { conditions: ['browser', 'import', 'module', 'default'],
esbuildOptions: { },
target: 'esnext', // SSR has its own separate ssr.resolve.conditions that must be explicitly set since not follow global resolve conditions.
}, ssr: {
},
resolve: { resolve: {
// Configure to use browser version of @xocash/stack // Configure to use browser version of @xocash/stack
conditions: ['browser', 'import', 'module', 'default'], conditions: ['browser', 'import', 'module', 'default'],
}, },
// SSR has its own separate ssr.resolve.conditions that must be explicitly set since not follow global resolve conditions.
ssr: {
resolve: {
// Configure to use browser version of @xocash/stack
conditions: ['browser', 'import', 'module', 'default'],
},
},
}, },
}), },
); });
+50 -4
View File
@@ -1,15 +1,61 @@
// https://vitepress.dev/guide/custom-theme import { h, nextTick, watch } from 'vue';
import type { Theme } from 'vitepress'; import type { Theme } from 'vitepress';
import { useData } from 'vitepress';
import DefaultTheme from 'vitepress/theme'; import DefaultTheme from 'vitepress/theme';
import Layout from './Layout.vue'; import Layout from './Layout.vue';
import './style.css'; import './style.css';
import './custom.css'; import './custom.css';
import './tools.css'; import './tools.css';
import './mermaid-overrides.css'; import './mermaid-overrides.css';
import './mermaid-lightbox.client';
import { createMermaidRenderer } from 'vitepress-mermaid-renderer';
export default { export default {
extends: DefaultTheme, extends: DefaultTheme,
Layout,
enhanceApp({ _app, _router, _siteData }) {}, Layout: () => {
const { isDark, localeIndex } = useData();
const initMermaid = () => {
const mermaidRenderer = createMermaidRenderer({
theme: isDark.value ? 'dark' : 'forest',
});
mermaidRenderer.setToolbar({
showLanguageLabel: false,
downloadFormat: 'svg',
fullscreenMode: 'browser',
desktop: {
copyCode: 'enabled',
toggleFullscreen: 'enabled',
resetView: 'enabled',
zoomOut: 'enabled',
zoomIn: 'enabled',
zoomLevel: 'enabled',
download: 'enabled',
},
fullscreen: {
copyCode: 'disabled',
toggleFullscreen: 'enabled',
resetView: 'disabled',
zoomLevel: 'disabled',
download: 'enabled',
},
});
};
nextTick(initMermaid);
watch(
() => [isDark.value, localeIndex.value] as const,
() => {
initMermaid();
},
);
return h(Layout);
},
enhanceApp() {},
} satisfies Theme; } satisfies Theme;
+12 -3
View File
@@ -36,20 +36,26 @@ export class Encrypter {
...opts, ...opts,
}; };
// Something about the browser crypto API. It needs a "clean" Uint8Array, where TS could see the construction or something
const cleanKey = new Uint8Array(key);
// Create a key derivation key // Create a key derivation key
const keyMaterialBase = await crypto.subtle.importKey( const keyMaterialBase = await crypto.subtle.importKey(
'raw', 'raw',
key, cleanKey,
'PBKDF2', 'PBKDF2',
false, false,
['deriveBits', 'deriveKey'], ['deriveBits', 'deriveKey'],
); );
// Something about the browser crypto API. It needs a "clean" Uint8Array, where TS could see the construction or something
const cleanSalt = new Uint8Array(encryptionOpts.salt);
// Derive the actual encryption key // Derive the actual encryption key
const derivedKey = await crypto.subtle.deriveKey( const derivedKey = await crypto.subtle.deriveKey(
{ {
name: 'PBKDF2', name: 'PBKDF2',
salt: encryptionOpts.salt, salt: cleanSalt,
iterations: encryptionOpts.iterations, iterations: encryptionOpts.iterations,
hash: 'SHA-256', hash: 'SHA-256',
}, },
@@ -87,6 +93,9 @@ export class Encrypter {
// Generate random IV // Generate random IV
const iv = crypto.getRandomValues(new Uint8Array(this.opts.ivLength)); const iv = crypto.getRandomValues(new Uint8Array(this.opts.ivLength));
// Something about the browser crypto API. It needs a "clean" Uint8Array, where TS could see the construction or something
const cleanData = new Uint8Array(data);
// Encrypt the data // Encrypt the data
const encryptedData = await crypto.subtle.encrypt( const encryptedData = await crypto.subtle.encrypt(
{ {
@@ -94,7 +103,7 @@ export class Encrypter {
iv, iv,
}, },
this.key, this.key,
data, cleanData,
); );
// Combine IV and encrypted data // Combine IV and encrypted data
+1 -1
View File
@@ -41,7 +41,7 @@
"@generalprotocols/oracle-client": "^0.0.1-development.11878773826", "@generalprotocols/oracle-client": "^0.0.1-development.11878773826",
"@xocash/primitives": "*", "@xocash/primitives": "*",
"async-mutex": "^0.5.0", "async-mutex": "^0.5.0",
"better-sqlite3": "^11.8.1", "better-sqlite3": "^13.0.3",
"idb": "8.0.3", "idb": "8.0.3",
"json-to-graphql-query": "^2.3.0", "json-to-graphql-query": "^2.3.0",
"msgpackr": "^1.11.2", "msgpackr": "^1.11.2",
@@ -297,10 +297,16 @@ export class StoreRemoteHTTP<T = unknown> extends BaseStore<
'X-Timestamp': timestamp.toString(), 'X-Timestamp': timestamp.toString(),
}; };
const requestOptions = { const body = payload ? pack(payload) : pack({});
const bodyBuffer = new Uint8Array(body.buffer);
const requestOptions: RequestInit = {
method: 'POST', method: 'POST',
headers, headers,
body: payload ? pack(payload) : pack({}), // Because we support the DOM types, it doesnt register this UInt8Array as an acceptable BodyInit, so we need to cast it to unknown.
// Could be fixed in a later version of typescript, but there are build issues atm.
// TODO: (not really, unless we intend to use this code) Remove the type cast.
body: bodyBuffer as unknown as BodyInit,
}; };
// Use the URL class to compile the full path. // Use the URL class to compile the full path.
@@ -4,19 +4,18 @@
// However, Chaingraph backend is currently broken for many queries, so let's not do this yet. // However, Chaingraph backend is currently broken for many queries, so let's not do this yet.
import { beforeAll, describe, it, expect } from 'vitest'; import { beforeAll, describe, it, expect } from 'vitest';
import { import { BaseBlockchain, BlockchainElectrum } from '../../src/index.js';
BaseBlockchain,
BlockchainChaingraph,
BlockchainElectrum,
} from '../../src/index.js';
const adapters: Record< const adapters: Record<
string, string,
() => Promise<BaseBlockchain<unknown, unknown>> () => Promise<BaseBlockchain<unknown, unknown>>
> = { > = {
chaingraph: async () => { // Commenting out since the chaingraph endopoints we are hitting are currently broken.
return await BlockchainChaingraph.from(); // demo won't allow non-browser clients
}, // Pat's server isn't responding to block height requests correctly (or we are using an old version, which the demo server is also running).
// chaingraph: async () => {
// return await BlockchainChaingraph.from();
// },
electrum: async () => { electrum: async () => {
return await BlockchainElectrum.from(); return await BlockchainElectrum.from();
}, },
@@ -59,7 +58,7 @@ describe.each(Object.entries(adapters))('%s Adapter', (name, createAdapter) => {
}); });
it(`${name}.fetchBlockHeader() `, async () => { it(`${name}.fetchBlockHeader() `, async () => {
const result = await blockchain.fetchBlockHeader(testBlockHeight); const result = await blockchain.fetchBlockHeader(1);
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(result.toHex()).toBeTypeOf('string'); expect(result.toHex()).toBeTypeOf('string');
@@ -6,7 +6,7 @@ import {
monitorSyncTipsSchema, monitorSyncTipsSchema,
} from '../../src/blockchain/blockchain-chaingraph-helpers.js'; } from '../../src/blockchain/blockchain-chaingraph-helpers.js';
const chaingraphServer = 'https://demo.chaingraph.cash/v1/graphql'; const chaingraphServer = 'https://gql.chaingraph.pat.mn/v1/graphql';
describe('GraphQLClient', () => { describe('GraphQLClient', () => {
const graphQLClient = new GraphQLClient(chaingraphServer); const graphQLClient = new GraphQLClient(chaingraphServer);