From 15035498c85286a661f1073fdd34423f01128b54 Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Thu, 21 Aug 2025 15:35:55 -0600 Subject: [PATCH 1/3] chore(changeset): experimental release 1 --- .changeset/experimental-release-1.md | 8 + docs/examples/address.ts | 23 ++ docs/examples/data.ts | 51 +++ docs/examples/data/basic-construction.ts | 34 ++ docs/examples/data/bytes-validate.ts | 12 + docs/examples/data/cbor-encoding-options.ts | 50 +++ .../data/complex-nested-structures.ts | 65 ++++ docs/examples/data/error-handling-patterns.ts | 71 ++++ docs/examples/data/nested-canonical.ts | 28 ++ docs/examples/data/roundtrip.ts | 11 + docs/examples/data/working-with-maps.ts | 40 +++ docs/next.config.js | 15 - docs/next.config.mjs | 18 + docs/package.json | 10 +- docs/pages/getting-started/address.mdx | 13 +- docs/pages/getting-started/data.mdx | 87 ----- .../data/basic-construction.mdx | 35 ++ .../getting-started/data/bytes-validate.mdx | 13 + .../data/cbor-encoding-options.mdx | 51 +++ .../data/complex-nested-structures.mdx | 66 ++++ .../data/error-handling-patterns.mdx | 71 ++++ docs/pages/getting-started/data/index.mdx | 10 + .../getting-started/data/nested-canonical.mdx | 29 ++ docs/pages/getting-started/data/roundtrip.mdx | 12 + .../data/working-with-maps.mdx | 41 +++ docs/scripts/copy-evolution-docs.mjs | 122 ------- docs/scripts/copy-evolution-docs.ts | 117 +++++++ docs/scripts/generate-getting-started.ts | 312 ++++++++++++++++++ docs/scripts/run-examples-and-generate.sh | 131 ++++++++ docs/test/examples.test.ts | 198 ----------- package.json | 3 +- pnpm-lock.yaml | 9 + 32 files changed, 1326 insertions(+), 430 deletions(-) create mode 100644 .changeset/experimental-release-1.md create mode 100644 docs/examples/address.ts create mode 100644 docs/examples/data.ts create mode 100644 docs/examples/data/basic-construction.ts create mode 100644 docs/examples/data/bytes-validate.ts create mode 100644 docs/examples/data/cbor-encoding-options.ts create mode 100644 docs/examples/data/complex-nested-structures.ts create mode 100644 docs/examples/data/error-handling-patterns.ts create mode 100644 docs/examples/data/nested-canonical.ts create mode 100644 docs/examples/data/roundtrip.ts create mode 100644 docs/examples/data/working-with-maps.ts delete mode 100644 docs/next.config.js create mode 100644 docs/next.config.mjs delete mode 100644 docs/pages/getting-started/data.mdx create mode 100644 docs/pages/getting-started/data/basic-construction.mdx create mode 100644 docs/pages/getting-started/data/bytes-validate.mdx create mode 100644 docs/pages/getting-started/data/cbor-encoding-options.mdx create mode 100644 docs/pages/getting-started/data/complex-nested-structures.mdx create mode 100644 docs/pages/getting-started/data/error-handling-patterns.mdx create mode 100644 docs/pages/getting-started/data/index.mdx create mode 100644 docs/pages/getting-started/data/nested-canonical.mdx create mode 100644 docs/pages/getting-started/data/roundtrip.mdx create mode 100644 docs/pages/getting-started/data/working-with-maps.mdx delete mode 100644 docs/scripts/copy-evolution-docs.mjs create mode 100644 docs/scripts/copy-evolution-docs.ts create mode 100644 docs/scripts/generate-getting-started.ts create mode 100755 docs/scripts/run-examples-and-generate.sh delete mode 100644 docs/test/examples.test.ts diff --git a/.changeset/experimental-release-1.md b/.changeset/experimental-release-1.md new file mode 100644 index 00000000..ceea5d7a --- /dev/null +++ b/.changeset/experimental-release-1.md @@ -0,0 +1,8 @@ +--- +"@evolution-sdk/evolution": minor +--- + +Experimental release 1: +- Introduce experimental modules and docs flow +- Add runnable Data examples with MDX generation +- ESM Next/Nextra configuration for docs diff --git a/docs/examples/address.ts b/docs/examples/address.ts new file mode 100644 index 00000000..f3fa63cc --- /dev/null +++ b/docs/examples/address.ts @@ -0,0 +1,23 @@ +// Examples for Address getting-started page +// Run with: pnpm -w -C docs ts-node examples/address.ts (or a small runner) + +// #region address-example +import assert from "node:assert/strict" +import { Address } from "@evolution-sdk/evolution" + +const hexAddress = "60ba1d6b6283c219a0530e3682c316215d55819cf97bbf26552c6f8530" +const expectedBech32 = "addr_test1vzap66mzs0ppngznpcmg9scky9w4tqvul9am7fj493hc2vq4ry02m" + +// Decode from hex +const address = Address.fromHex(hexAddress) + +// Encode to bech32 +const actualBech32 = Address.toBech32(address) + +// Verify the conversion is correct +assert.strictEqual(actualBech32, expectedBech32) +// #endregion address-example + +if (import.meta.url === `file://${process.argv[1]}`) { + console.log("Address example OK") +} diff --git a/docs/examples/data.ts b/docs/examples/data.ts new file mode 100644 index 00000000..15b8dbaf --- /dev/null +++ b/docs/examples/data.ts @@ -0,0 +1,51 @@ +// Examples for Data getting-started page + +// #region data-nested-canonical +import assert from "node:assert/strict" +import { CBOR, Data } from "@evolution-sdk/evolution" +// Create a complex nested data structure with: +// - Constructor with index 1 containing multiple fields +// - Nested constructors with different indices +// - A Map with mixed key-value pairs +// - An array of numbers +const nestedUnsortedData = new Data.Constr({ + index: 1n, + fields: [ + // Nested constructor: 121_0([123_0([])]) + new Data.Constr({ + index: 0n, + fields: [ + new Data.Constr({ + index: 2n, + fields: [] + }) + ] + }), + // Map with unsorted keys (will be sorted in canonical mode) + new Map([ + ["deadbeef01", new Data.Constr({ index: 0n, fields: [] })], + ["beef", 19n], + ["deadbeef03", new Data.Constr({ index: 1n, fields: [] })] + ]), + // Array of numbers + [10n, 5n, 2n, 3n, 1n, 4n] + ] +}) + +// Encode using default options (indefinite-length encoding for Data) +const cborHex = Data.toCBORHex(nestedUnsortedData) + +const decodedData = Data.fromCBORHex(cborHex) + +// Create a canonical codec for deterministic encoding +// This ensures consistent output and sorted map keys +// Encode using canonical mode (definite-length, sorted keys) +const canonicalCborHex = Data.toCBORHex(nestedUnsortedData, CBOR.CANONICAL_OPTIONS) + +// Verify that decoding works correctly +assert.deepStrictEqual(decodedData, nestedUnsortedData) +// #endregion data-nested-canonical + +if (import.meta.url === `file://${process.argv[1]}`) { + console.log("Data example OK") +} diff --git a/docs/examples/data/basic-construction.ts b/docs/examples/data/basic-construction.ts new file mode 100644 index 00000000..174ca0c1 --- /dev/null +++ b/docs/examples/data/basic-construction.ts @@ -0,0 +1,34 @@ +// @title: Basic Data Construction +// @description: Learn how to create fundamental Data types using constructors. +import assert from "node:assert/strict" +import { Data } from "@evolution-sdk/evolution" + +// #region main +// Create a simple constructor with no fields +const unit = new Data.Constr({ index: 0n, fields: [] }) +console.log("Unit constructor:", unit) + +// Create a constructor with primitive fields +const person = new Data.Constr({ + index: 1n, + fields: ["416c696365", 30n] // 'Alice' as hex and age +}) +console.log("Person constructor:", person) + +// Create a constructor with mixed field types +const record = new Data.Constr({ + index: 2n, + fields: [ + "deadbeef", // bytes as hex string (ByteArray) + 42n, // big integer (Int) + [1n, 2n, 3n], // array of big integers (List) + new Data.Constr({ index: 0n, fields: [] }) // nested constructor + ] +}) + +// Verify the construction worked +assert.equal(person.index, 1n) +assert.equal(person.fields.length, 2) +assert.equal(person.fields[0], "416c696365") // 'Alice' in hex +assert.equal(person.fields[1], 30n) +// #endregion main diff --git a/docs/examples/data/bytes-validate.ts b/docs/examples/data/bytes-validate.ts new file mode 100644 index 00000000..aae1d7b5 --- /dev/null +++ b/docs/examples/data/bytes-validate.ts @@ -0,0 +1,12 @@ +// @title: Validate bytes +// @description: Quick check for hex-like bytes strings using Data.isBytes. +import assert from "node:assert/strict" +import { Data } from "@evolution-sdk/evolution" + +// #region main +const hex = "deadbeef" +assert.equal(Data.isBytes(hex), true) + +const invalid = "not-hex" +assert.equal(Data.isBytes(invalid), false) +// #endregion main diff --git a/docs/examples/data/cbor-encoding-options.ts b/docs/examples/data/cbor-encoding-options.ts new file mode 100644 index 00000000..f1b13a3c --- /dev/null +++ b/docs/examples/data/cbor-encoding-options.ts @@ -0,0 +1,50 @@ +// @title: CBOR Encoding Options +// @description: Compare different CBOR encoding strategies for the same data. +import assert from "node:assert/strict" +import { CBOR, Data } from "@evolution-sdk/evolution" + +// #region main +// Create complex data with unsorted elements (Maps should be standalone, not in constructor fields) +const unsortedMap = new Map([ + ["7a65627261", 1n], // 'zebra' in hex + ["6170706c65", 2n], // 'apple' in hex + ["62616e616e61", 3n] // 'banana' in hex +]) + +// Create a constructor with only valid field types +const complexData = new Data.Constr({ + index: 1n, + fields: [ + // List with mixed order + [100n, 1n, 50n, 25n], + "deadbeef", + 42n // additional data + ] +}) + +// Standard encoding (preserves original order) +const standardHex = Data.toCBORHex(complexData) +console.log("Standard CBOR:", standardHex) + +// Canonical encoding (sorted for deterministic output) +const canonicalHex = Data.toCBORHex(complexData, CBOR.CANONICAL_OPTIONS) +console.log("Canonical CBOR:", canonicalHex) + +// Test with the standalone map +const mapStandardHex = Data.toCBORHex(unsortedMap) +const mapCanonicalHex = Data.toCBORHex(unsortedMap, CBOR.CANONICAL_OPTIONS) + +// Both should decode to equivalent data +const fromStandard = Data.fromCBORHex(standardHex) +const fromCanonical = Data.fromCBORHex(canonicalHex) + +assert.deepStrictEqual(fromStandard, complexData) +assert.deepStrictEqual(fromCanonical, complexData) +assert.deepStrictEqual(fromStandard, fromCanonical) + +// Demonstrate that canonical encoding is deterministic +const secondCanonical = Data.toCBORHex(complexData, CBOR.CANONICAL_OPTIONS) +assert.equal(canonicalHex, secondCanonical) + +console.log("Map encoding also works:", mapStandardHex.length > 0) +// #endregion main diff --git a/docs/examples/data/complex-nested-structures.ts b/docs/examples/data/complex-nested-structures.ts new file mode 100644 index 00000000..4e49fd88 --- /dev/null +++ b/docs/examples/data/complex-nested-structures.ts @@ -0,0 +1,65 @@ +// @title: Complex Nested Structures +// @description: Build sophisticated nested data structures with multiple levels and types. +import assert from "node:assert/strict" +import { Data } from "@evolution-sdk/evolution" + +// #region main +// Create a complex user profile with nested data using only valid Data types +const userProfile = new Data.Constr({ + index: 0n, // User constructor + fields: [ + "616c696365", // username 'alice' in hex + new Data.Constr({ + index: 1n, // Profile constructor + fields: [ + 25n, // age + "deadbeef", // some profile data as hex + // Nested preferences constructor + new Data.Constr({ + index: 2n, // Preferences constructor + fields: [ + 1n, // notification setting (1 = enabled) + 0n, // theme setting (0 = light, 1 = dark) + 8n // timezone offset + ] + }) + ] + }) + ] +}) + +// Create a transaction record with complex metadata +const transaction = new Data.Constr({ + index: 10n, // Transaction constructor + fields: [ + "deadbeef1234", // transaction hash + 1000000n, // amount in microADA + new Data.Constr({ + index: 11n, // Address constructor + fields: ["616464723174657374"] // address data in hex + }), + // Simple metadata as nested constructor + new Data.Constr({ + index: 12n, // Metadata constructor + fields: [ + 1640995200n, // timestamp + "7061796d656e74", // 'payment' as hex string + 1n // status code + ] + }) + ] +}) + +// Test deep structure access +assert.equal(userProfile.index, 0n) +assert.equal(userProfile.fields[0], "616c696365") // alice in hex + +// Verify nested constructor +const profileData = userProfile.fields[1] as Data.Constr +assert.equal(profileData.index, 1n) +assert.equal(profileData.fields[0], 25n) + +console.log("Complex structures created successfully") +console.log("User profile index:", userProfile.index) +console.log("Transaction amount:", transaction.fields[1]) +// #endregion main diff --git a/docs/examples/data/error-handling-patterns.ts b/docs/examples/data/error-handling-patterns.ts new file mode 100644 index 00000000..04822328 --- /dev/null +++ b/docs/examples/data/error-handling-patterns.ts @@ -0,0 +1,71 @@ +// @title: Error Handling Patterns +// @description: Use Either patterns for safe data operations with proper error handling. +import assert from "node:assert/strict" +import { Either, pipe } from "effect" +import { Data } from "@evolution-sdk/evolution" + +// #region main +// Use built-in Either-based functions for safe operations +const safeData = new Data.Constr({ index: 0n, fields: ["74657374", 42n] }) // 'test' as hex + +// Safe encoding using Data.Either namespace +const encodingResult = Data.Either.toCBORHex(safeData) +assert.equal(Either.isRight(encodingResult), true) + +// Extract values using Either.match for type safety +const encodedHex = Either.match(encodingResult, { + onLeft: (error: Data.DataError) => { + throw new Error(`Encoding failed: ${error.message}`) + }, + onRight: (hex: string) => { + console.log("Encoded successfully:", hex) + return hex + } +}) + +// Safe decoding using the encoded hex +const decodingResult = Data.Either.fromCBORHex(encodedHex) +assert.equal(Either.isRight(decodingResult), true) + +Either.match(decodingResult, { + onLeft: (error: Data.DataError) => { + throw new Error(`Decoding failed: ${error.message}`) + }, + onRight: (decoded: Data.Data) => { + console.log("Decoded successfully") + assert.deepStrictEqual(decoded, safeData) + } +}) + +// Test error handling with invalid input +const invalidResult = Data.Either.fromCBORHex("invalid-hex-data") +assert.equal(Either.isLeft(invalidResult), true) + +Either.match(invalidResult, { + onLeft: (error: Data.DataError) => { + console.log("Expected error:", error.message) + }, + onRight: () => { + throw new Error("Should not succeed with invalid input") + } +}) + +// Compose operations using pipe and Either +const processDataSafely = (data: Data.Data) => + pipe( + Data.Either.toCBORHex(data), + Either.flatMap((hex: string) => Data.Either.fromCBORHex(hex)), + Either.map((result: Data.Data) => `Roundtrip successful`) + ) + +const pipeResult = processDataSafely(safeData) +assert.equal(Either.isRight(pipeResult), true) + +// Pattern matching for comprehensive error handling +const finalMessage = Either.match(pipeResult, { + onLeft: (error: Data.DataError) => `Operation failed: ${error.message}`, + onRight: (value: string) => `Operation succeeded: ${value}` +}) + +console.log("Final result:", finalMessage) +// #endregion main diff --git a/docs/examples/data/nested-canonical.ts b/docs/examples/data/nested-canonical.ts new file mode 100644 index 00000000..bebc7cc7 --- /dev/null +++ b/docs/examples/data/nested-canonical.ts @@ -0,0 +1,28 @@ +// @title: Canonical nested structure +// @description: Complex nested Data encoding with canonical CBOR options. +import assert from "node:assert/strict" +import { CBOR, Data } from "@evolution-sdk/evolution" + +// #region main +const nestedUnsortedData = new Data.Constr({ + index: 1n, + fields: [ + new Data.Constr({ + index: 0n, + fields: [new Data.Constr({ index: 2n, fields: [] })] + }), + new Map([ + ["deadbeef01", new Data.Constr({ index: 0n, fields: [] })], + ["beef", 19n], + ["deadbeef03", new Data.Constr({ index: 1n, fields: [] })] + ]), + [10n, 5n, 2n, 3n, 1n, 4n] + ] +}) + +const cborHex = Data.toCBORHex(nestedUnsortedData) +const decoded = Data.fromCBORHex(cborHex) +const canonicalCborHex = Data.toCBORHex(nestedUnsortedData, CBOR.CANONICAL_OPTIONS) + +assert.deepStrictEqual(decoded, nestedUnsortedData) +// #endregion main diff --git a/docs/examples/data/roundtrip.ts b/docs/examples/data/roundtrip.ts new file mode 100644 index 00000000..72e72856 --- /dev/null +++ b/docs/examples/data/roundtrip.ts @@ -0,0 +1,11 @@ +// @title: Roundtrip encode/decode +// @description: Encode a Data value to CBOR hex and decode back. +import assert from "node:assert/strict" +import { CBOR, Data } from "@evolution-sdk/evolution" + +// #region main +const original = new Data.Constr({ index: 0n, fields: ["beef", 19n] }) +const hexCbor = Data.toCBORHex(original, CBOR.CANONICAL_OPTIONS) +const back = Data.fromCBORHex(hexCbor) +assert.deepStrictEqual(back, original) +// #endregion main diff --git a/docs/examples/data/working-with-maps.ts b/docs/examples/data/working-with-maps.ts new file mode 100644 index 00000000..3ad66789 --- /dev/null +++ b/docs/examples/data/working-with-maps.ts @@ -0,0 +1,40 @@ +// @title: Working with Maps +// @description: Create and manipulate Data Maps with key-value pairs. +import assert from "node:assert/strict" +import { Data } from "@evolution-sdk/evolution" + +// #region main +// Create a simple map with hex string keys and integer values +const userAges = new Map([ + ["616c696365", 25n], // 'alice' in hex + ["626f62", 30n], // 'bob' in hex + ["636861726c6965", 35n] // 'charlie' in hex +]) + +console.log("User ages map:", userAges) + +// Create a map with constructor keys and hex string values +const statusMap = new Map([ + [new Data.Constr({ index: 0n, fields: [] }), "70656e64696e67"], // 'pending' in hex + [new Data.Constr({ index: 1n, fields: [] }), "617070726f766564"], // 'approved' in hex + [new Data.Constr({ index: 2n, fields: [] }), "72656a6563746564"] // 'rejected' in hex +]) + +// Demonstrate map usage - Maps are Data types themselves, not constructor fields +console.log("Status for constructor 0:", statusMap.get(new Data.Constr({ index: 0n, fields: [] }))) + +// Create a constructor that references the maps via indexes or simple structure +const dataRecord = new Data.Constr({ + index: 1n, + fields: [ + 25n, // alice's age directly + "deadbeef", // some data + 42n // more data + ] +}) + +// Verify map operations +assert.equal(userAges.get("616c696365"), 25n) // alice's age +assert.equal(userAges.size, 3) +assert.equal(dataRecord.fields.length, 3) +// #endregion main diff --git a/docs/next.config.js b/docs/next.config.js deleted file mode 100644 index ac31e0f7..00000000 --- a/docs/next.config.js +++ /dev/null @@ -1,15 +0,0 @@ -const withNextra = require("nextra")({ - theme: "nextra-theme-docs", - themeConfig: "./theme.config.tsx" -}) - -module.exports = withNextra({ - reactStrictMode: true, - trailingSlash: true, - output: "export", - distDir: "out", - images: { - unoptimized: true - }, - basePath: process.env.GITHUB_ACTIONS ? "/evolution-sdk" : "", -}) diff --git a/docs/next.config.mjs b/docs/next.config.mjs new file mode 100644 index 00000000..97546bb2 --- /dev/null +++ b/docs/next.config.mjs @@ -0,0 +1,18 @@ +import nextra from 'nextra' + +const withNextra = nextra({ + theme: 'nextra-theme-docs', + themeConfig: './theme.config.tsx', +}) + +const isCI = !!process.env.GITHUB_ACTIONS +const isProd = process.env.NODE_ENV === 'production' + +export default withNextra({ + reactStrictMode: true, + trailingSlash: true, + output: 'export', + distDir: 'out', + images: { unoptimized: true }, + basePath: isCI && isProd ? '/evolution-sdk' : '', +}) diff --git a/docs/package.json b/docs/package.json index 6009766c..f61307d9 100644 --- a/docs/package.json +++ b/docs/package.json @@ -2,14 +2,17 @@ "name": "@evolution-sdk/docs", "version": "0.1.0", "private": true, + "type": "module", "description": "Documentation for Evolution SDK", "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "export": "next build", - "copy-evolution-docs": "node scripts/copy-evolution-docs.mjs", - "prebuild": "pnpm run copy-evolution-docs" + "copy-evolution-docs": "tsx scripts/copy-evolution-docs.ts", + "snippets": "tsx scripts/generate-getting-started.ts", + "test-examples": "./scripts/run-examples-and-generate.sh", + "prebuild": "pnpm run copy-evolution-docs && pnpm run test-examples" }, "dependencies": { "next": "^14.0.0", @@ -19,9 +22,12 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@evolution-sdk/evolution": "workspace:*", "@types/node": "^20.0.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", + "effect": "^3.17.3", + "tsx": "^4.20.3", "typescript": "^5.0.0" } } diff --git a/docs/pages/getting-started/address.mdx b/docs/pages/getting-started/address.mdx index a5b80c81..41421ac8 100644 --- a/docs/pages/getting-started/address.mdx +++ b/docs/pages/getting-started/address.mdx @@ -4,23 +4,28 @@ Address module provides functionality for working with address types in Cardano. ## Address Example +{/* BEGIN:snippet file=address.ts region=address-example lang=typescript */} + ```typescript +// #region address-example +import assert from "node:assert/strict" import { Address } from "@evolution-sdk/evolution" const hexAddress = "60ba1d6b6283c219a0530e3682c316215d55819cf97bbf26552c6f8530" const expectedBech32 = "addr_test1vzap66mzs0ppngznpcmg9scky9w4tqvul9am7fj493hc2vq4ry02m" // Decode from hex -const address = Address.Codec.Decode.hex(hexAddress) +const address = Address.fromHex(hexAddress) // Encode to bech32 -const actualBech32 = Address.Codec.Encode.bech32(address) +const actualBech32 = Address.toBech32(address) // Verify the conversion is correct -assert(actualBech32 === expectedBech32) - +assert.strictEqual(actualBech32, expectedBech32) ``` +{/* END:snippet */} + ## API Reference For detailed API documentation, see the generated TypeDoc documentation. \ No newline at end of file diff --git a/docs/pages/getting-started/data.mdx b/docs/pages/getting-started/data.mdx deleted file mode 100644 index ac5e4821..00000000 --- a/docs/pages/getting-started/data.mdx +++ /dev/null @@ -1,87 +0,0 @@ -# Data - -Data module provides functionality for working with data types in Cardano. - -## Data Example - -```typescript -import { Data } from "@evolution-sdk/evolution" - -// Create a complex nested data structure with: -// - Constructor with index 1 containing multiple fields -// - Nested constructors with different indices -// - A Map with mixed key-value pairs -// - An array of numbers -const nestedUnsortedData = new Data.Constr({ - index: 1n, - fields: [ - // Nested constructor: 121_0([123_0([])]) - new Data.Constr({ - index: 0n, - fields: [ - new Data.Constr({ - index: 2n, - fields: [] - }) - ] - }), - // Map with unsorted keys (will be sorted in canonical mode) - new Map([ - ["deadbeef01", new Data.Constr({ index: 0n, fields: [] })], - ["beef", 19n], - ["deadbeef03", new Data.Constr({ index: 1n, fields: [] })] - ]), - // Array of numbers - [10n, 5n, 2n, 3n, 1n, 4n] - ] -}) - -// Encode using default codec (indefinite-length encoding) -const cborHex = Data.Codec().Encode.cborHex(nestedUnsortedData) -// Output: d87a9fd8799fd87b80ffbf45deadbeef01d8798042beef1345deadbeef03d87a80ff9f0a0502030104ffff - -// CBOR diagnostic notation (indefinite-length): -// 122_0([_ -// 121_0([_ 123_0([])]), -// {_ -// h'deadbeef01': 121_0([]), -// h'beef': 19, -// h'deadbeef03': 122_0([]), -// }, -// [_ 10, 5, 2, 3, 1, 4], -// ]) -// Visualize at: https://cbor.nemo157.com/ - -const decodedData = Data.Codec().Decode.cborHex(cborHex) - -// Create a canonical codec for deterministic encoding -// This ensures consistent output and sorted map keys -const canonicalCodec = Data.Codec({ - options: { - mode: "canonical" - } -}) - -// Encode using canonical mode (definite-length, sorted keys) -const canonicalCborHex = canonicalCodec.Encode.cborHex(nestedUnsortedData) -// Output: d87a83d87981d87b80a342beef1345deadbeef01d8798045deadbeef03d87a80860a0502030104 - -// CBOR diagnostic notation (canonical/definite-length): -// 122_0([ -// 121_0([123_0([])]), -// { -// h'beef': 19, ← Keys are now sorted -// h'deadbeef01': 121_0([]), -// h'deadbeef03': 122_0([]), -// }, -// [10, 5, 2, 3, 1, 4], ← Definite-length array -// ]) - -// Verify that decoding works correctly -assert.deepStrictEqual(decodedData, nestedUnsortedData, "Decoded data should match original") - -``` - -## API Reference - -For detailed API documentation, see the generated TypeDoc documentation. \ No newline at end of file diff --git a/docs/pages/getting-started/data/basic-construction.mdx b/docs/pages/getting-started/data/basic-construction.mdx new file mode 100644 index 00000000..b4064bd0 --- /dev/null +++ b/docs/pages/getting-started/data/basic-construction.mdx @@ -0,0 +1,35 @@ +# Basic Data Construction + +Learn how to create fundamental Data types using constructors. + +```typescript +// #region main +// Create a simple constructor with no fields +const unit = new Data.Constr({ index: 0n, fields: [] }) +console.log("Unit constructor:", unit) + +// Create a constructor with primitive fields +const person = new Data.Constr({ + index: 1n, + fields: ["416c696365", 30n] // 'Alice' as hex and age +}) +console.log("Person constructor:", person) + +// Create a constructor with mixed field types +const record = new Data.Constr({ + index: 2n, + fields: [ + "deadbeef", // bytes as hex string (ByteArray) + 42n, // big integer (Int) + [1n, 2n, 3n], // array of big integers (List) + new Data.Constr({ index: 0n, fields: [] }) // nested constructor + ] +}) + +// Verify the construction worked +assert.equal(person.index, 1n) +assert.equal(person.fields.length, 2) +assert.equal(person.fields[0], "416c696365") // 'Alice' in hex +assert.equal(person.fields[1], 30n) + +``` diff --git a/docs/pages/getting-started/data/bytes-validate.mdx b/docs/pages/getting-started/data/bytes-validate.mdx new file mode 100644 index 00000000..4941050c --- /dev/null +++ b/docs/pages/getting-started/data/bytes-validate.mdx @@ -0,0 +1,13 @@ +# Validate bytes + +Quick check for hex-like bytes strings using Data.isBytes. + +```typescript +// #region main +const hex = "deadbeef" +assert.equal(Data.isBytes(hex), true) + +const invalid = "not-hex" +assert.equal(Data.isBytes(invalid), false) + +``` diff --git a/docs/pages/getting-started/data/cbor-encoding-options.mdx b/docs/pages/getting-started/data/cbor-encoding-options.mdx new file mode 100644 index 00000000..e9ef33af --- /dev/null +++ b/docs/pages/getting-started/data/cbor-encoding-options.mdx @@ -0,0 +1,51 @@ +# CBOR Encoding Options + +Compare different CBOR encoding strategies for the same data. + +```typescript +// #region main +// Create complex data with unsorted elements (Maps should be standalone, not in constructor fields) +const unsortedMap = new Map([ + ["7a65627261", 1n], // 'zebra' in hex + ["6170706c65", 2n], // 'apple' in hex + ["62616e616e61", 3n] // 'banana' in hex +]) + +// Create a constructor with only valid field types +const complexData = new Data.Constr({ + index: 1n, + fields: [ + // List with mixed order + [100n, 1n, 50n, 25n], + "deadbeef", + 42n // additional data + ] +}) + +// Standard encoding (preserves original order) +const standardHex = Data.toCBORHex(complexData) +console.log("Standard CBOR:", standardHex) + +// Canonical encoding (sorted for deterministic output) +const canonicalHex = Data.toCBORHex(complexData, CBOR.CANONICAL_OPTIONS) +console.log("Canonical CBOR:", canonicalHex) + +// Test with the standalone map +const mapStandardHex = Data.toCBORHex(unsortedMap) +const mapCanonicalHex = Data.toCBORHex(unsortedMap, CBOR.CANONICAL_OPTIONS) + +// Both should decode to equivalent data +const fromStandard = Data.fromCBORHex(standardHex) +const fromCanonical = Data.fromCBORHex(canonicalHex) + +assert.deepStrictEqual(fromStandard, complexData) +assert.deepStrictEqual(fromCanonical, complexData) +assert.deepStrictEqual(fromStandard, fromCanonical) + +// Demonstrate that canonical encoding is deterministic +const secondCanonical = Data.toCBORHex(complexData, CBOR.CANONICAL_OPTIONS) +assert.equal(canonicalHex, secondCanonical) + +console.log("Map encoding also works:", mapStandardHex.length > 0) + +``` diff --git a/docs/pages/getting-started/data/complex-nested-structures.mdx b/docs/pages/getting-started/data/complex-nested-structures.mdx new file mode 100644 index 00000000..9c5649d2 --- /dev/null +++ b/docs/pages/getting-started/data/complex-nested-structures.mdx @@ -0,0 +1,66 @@ +# Complex Nested Structures + +Build sophisticated nested data structures with multiple levels and types. + +```typescript +// #region main +// Create a complex user profile with nested data using only valid Data types +const userProfile = new Data.Constr({ + index: 0n, // User constructor + fields: [ + "616c696365", // username 'alice' in hex + new Data.Constr({ + index: 1n, // Profile constructor + fields: [ + 25n, // age + "deadbeef", // some profile data as hex + // Nested preferences constructor + new Data.Constr({ + index: 2n, // Preferences constructor + fields: [ + 1n, // notification setting (1 = enabled) + 0n, // theme setting (0 = light, 1 = dark) + 8n // timezone offset + ] + }) + ] + }) + ] +}) + +// Create a transaction record with complex metadata +const transaction = new Data.Constr({ + index: 10n, // Transaction constructor + fields: [ + "deadbeef1234", // transaction hash + 1000000n, // amount in microADA + new Data.Constr({ + index: 11n, // Address constructor + fields: ["616464723174657374"] // address data in hex + }), + // Simple metadata as nested constructor + new Data.Constr({ + index: 12n, // Metadata constructor + fields: [ + 1640995200n, // timestamp + "7061796d656e74", // 'payment' as hex string + 1n // status code + ] + }) + ] +}) + +// Test deep structure access +assert.equal(userProfile.index, 0n) +assert.equal(userProfile.fields[0], "616c696365") // alice in hex + +// Verify nested constructor +const profileData = userProfile.fields[1] as Data.Constr +assert.equal(profileData.index, 1n) +assert.equal(profileData.fields[0], 25n) + +console.log("Complex structures created successfully") +console.log("User profile index:", userProfile.index) +console.log("Transaction amount:", transaction.fields[1]) + +``` diff --git a/docs/pages/getting-started/data/error-handling-patterns.mdx b/docs/pages/getting-started/data/error-handling-patterns.mdx new file mode 100644 index 00000000..89a98514 --- /dev/null +++ b/docs/pages/getting-started/data/error-handling-patterns.mdx @@ -0,0 +1,71 @@ +# Error Handling Patterns + +Use Either patterns for safe data operations with proper error handling. + +```typescript +// #region main +// Use built-in Either-based functions for safe operations +const safeData = new Data.Constr({ index: 0n, fields: ["74657374", 42n] }) // 'test' as hex + +// Safe encoding using Data.Either namespace +const encodingResult = Data.Either.toCBORHex(safeData) +assert.equal(Either.isRight(encodingResult), true) + +// Extract values using Either.match for type safety +const encodedHex = Either.match(encodingResult, { + onLeft: (error: Data.DataError) => { + throw new Error(`Encoding failed: ${error.message}`) + }, + onRight: (hex: string) => { + console.log("Encoded successfully:", hex) + return hex + } +}) + +// Safe decoding using the encoded hex +const decodingResult = Data.Either.fromCBORHex(encodedHex) +assert.equal(Either.isRight(decodingResult), true) + +Either.match(decodingResult, { + onLeft: (error: Data.DataError) => { + throw new Error(`Decoding failed: ${error.message}`) + }, + onRight: (decoded: Data.Data) => { + console.log("Decoded successfully") + assert.deepStrictEqual(decoded, safeData) + } +}) + +// Test error handling with invalid input +const invalidResult = Data.Either.fromCBORHex("invalid-hex-data") +assert.equal(Either.isLeft(invalidResult), true) + +Either.match(invalidResult, { + onLeft: (error: Data.DataError) => { + console.log("Expected error:", error.message) + }, + onRight: () => { + throw new Error("Should not succeed with invalid input") + } +}) + +// Compose operations using pipe and Either +const processDataSafely = (data: Data.Data) => + pipe( + Data.Either.toCBORHex(data), + Either.flatMap((hex: string) => Data.Either.fromCBORHex(hex)), + Either.map((result: Data.Data) => `Roundtrip successful`) + ) + +const pipeResult = processDataSafely(safeData) +assert.equal(Either.isRight(pipeResult), true) + +// Pattern matching for comprehensive error handling +const finalMessage = Either.match(pipeResult, { + onLeft: (error: Data.DataError) => `Operation failed: ${error.message}`, + onRight: (value: string) => `Operation succeeded: ${value}` +}) + +console.log("Final result:", finalMessage) + +``` diff --git a/docs/pages/getting-started/data/index.mdx b/docs/pages/getting-started/data/index.mdx new file mode 100644 index 00000000..3104694c --- /dev/null +++ b/docs/pages/getting-started/data/index.mdx @@ -0,0 +1,10 @@ +# Data Examples + +- [Basic Data Construction](/getting-started/data/basic-construction) +- [Validate bytes](/getting-started/data/bytes-validate) +- [CBOR Encoding Options](/getting-started/data/cbor-encoding-options) +- [Complex Nested Structures](/getting-started/data/complex-nested-structures) +- [Error Handling Patterns](/getting-started/data/error-handling-patterns) +- [Canonical nested structure](/getting-started/data/nested-canonical) +- [Roundtrip encode/decode](/getting-started/data/roundtrip) +- [Working with Maps](/getting-started/data/working-with-maps) diff --git a/docs/pages/getting-started/data/nested-canonical.mdx b/docs/pages/getting-started/data/nested-canonical.mdx new file mode 100644 index 00000000..3e23befc --- /dev/null +++ b/docs/pages/getting-started/data/nested-canonical.mdx @@ -0,0 +1,29 @@ +# Canonical nested structure + +Complex nested Data encoding with canonical CBOR options. + +```typescript +// #region main +const nestedUnsortedData = new Data.Constr({ + index: 1n, + fields: [ + new Data.Constr({ + index: 0n, + fields: [new Data.Constr({ index: 2n, fields: [] })] + }), + new Map([ + ["deadbeef01", new Data.Constr({ index: 0n, fields: [] })], + ["beef", 19n], + ["deadbeef03", new Data.Constr({ index: 1n, fields: [] })] + ]), + [10n, 5n, 2n, 3n, 1n, 4n] + ] +}) + +const cborHex = Data.toCBORHex(nestedUnsortedData) +const decoded = Data.fromCBORHex(cborHex) +const canonicalCborHex = Data.toCBORHex(nestedUnsortedData, CBOR.CANONICAL_OPTIONS) + +assert.deepStrictEqual(decoded, nestedUnsortedData) + +``` diff --git a/docs/pages/getting-started/data/roundtrip.mdx b/docs/pages/getting-started/data/roundtrip.mdx new file mode 100644 index 00000000..221b7668 --- /dev/null +++ b/docs/pages/getting-started/data/roundtrip.mdx @@ -0,0 +1,12 @@ +# Roundtrip encode/decode + +Encode a Data value to CBOR hex and decode back. + +```typescript +// #region main +const original = new Data.Constr({ index: 0n, fields: ["beef", 19n] }) +const hexCbor = Data.toCBORHex(original, CBOR.CANONICAL_OPTIONS) +const back = Data.fromCBORHex(hexCbor) +assert.deepStrictEqual(back, original) + +``` diff --git a/docs/pages/getting-started/data/working-with-maps.mdx b/docs/pages/getting-started/data/working-with-maps.mdx new file mode 100644 index 00000000..e098a24e --- /dev/null +++ b/docs/pages/getting-started/data/working-with-maps.mdx @@ -0,0 +1,41 @@ +# Working with Maps + +Create and manipulate Data Maps with key-value pairs. + +```typescript +// #region main +// Create a simple map with hex string keys and integer values +const userAges = new Map([ + ["616c696365", 25n], // 'alice' in hex + ["626f62", 30n], // 'bob' in hex + ["636861726c6965", 35n] // 'charlie' in hex +]) + +console.log("User ages map:", userAges) + +// Create a map with constructor keys and hex string values +const statusMap = new Map([ + [new Data.Constr({ index: 0n, fields: [] }), "70656e64696e67"], // 'pending' in hex + [new Data.Constr({ index: 1n, fields: [] }), "617070726f766564"], // 'approved' in hex + [new Data.Constr({ index: 2n, fields: [] }), "72656a6563746564"] // 'rejected' in hex +]) + +// Demonstrate map usage - Maps are Data types themselves, not constructor fields +console.log("Status for constructor 0:", statusMap.get(new Data.Constr({ index: 0n, fields: [] }))) + +// Create a constructor that references the maps via indexes or simple structure +const dataRecord = new Data.Constr({ + index: 1n, + fields: [ + 25n, // alice's age directly + "deadbeef", // some data + 42n // more data + ] +}) + +// Verify map operations +assert.equal(userAges.get("616c696365"), 25n) // alice's age +assert.equal(userAges.size, 3) +assert.equal(dataRecord.fields.length, 3) + +``` diff --git a/docs/scripts/copy-evolution-docs.mjs b/docs/scripts/copy-evolution-docs.mjs deleted file mode 100644 index 94c6e91f..00000000 --- a/docs/scripts/copy-evolution-docs.mjs +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env node - -import * as fs from 'fs/promises'; -import * as path from 'path'; - -const sourceDir = path.resolve(process.cwd(), '../packages/evolution/docs/modules'); -const targetDir = path.resolve(process.cwd(), './pages/reference/modules'); - -async function ensureDirectoryExists(dir) { - try { - await fs.access(dir); - } catch (error) { - await fs.mkdir(dir, { recursive: true }); - console.log(`Created directory: ${dir}`); - } -} - -function convertMdToMdx(content) { - // Convert HTML class attributes to className for React/MDX compatibility - content = content.replace(/class="/g, 'className="'); - - // Just copy the files and rename them - no complex processing - return content; -} - -async function copyFile(src, dest) { - try { - const content = await fs.readFile(src, 'utf8'); - const convertedContent = convertMdToMdx(content); - await fs.writeFile(dest, convertedContent); - console.log(`Copied: ${path.relative(process.cwd(), src)} → ${path.relative(process.cwd(), dest)}`); - } catch (error) { - console.error(`Error copying ${src} to ${dest}: ${error.message}`); - throw error; - } -} - -async function copyDirectory(source, target) { - await ensureDirectoryExists(target); - - const entries = await fs.readdir(source, { withFileTypes: true }); - - for (const entry of entries) { - const sourcePath = path.join(source, entry.name); - let targetName = entry.name; - - // For .md files, convert to .mdx and remove .ts for cleaner navigation - if (entry.name.endsWith('.md')) { - if (entry.name.endsWith('.ts.md')) { - // Convert Address.ts.md to Address.mdx (remove .ts) - targetName = entry.name.replace(/\.ts\.md$/, '.mdx'); - } else { - targetName = entry.name.replace(/\.md$/, '.mdx'); - } - } - - const targetPath = path.join(target, targetName); - - if (entry.isDirectory()) { - await copyDirectory(sourcePath, targetPath); - } else { - await copyFile(sourcePath, targetPath); - } - } -} - -async function createMetaJson(directory) { - try { - const entries = await fs.readdir(directory); - const metaEntries = {}; - - // Filter only the .mdx files (converted from .md) - const mdxFiles = entries.filter(entry => - entry.endsWith('.mdx') && entry !== 'index.mdx' - ); - - // Sort alphabetically - mdxFiles.sort(); - - // Create meta entries for each file using simple string format - for (const file of mdxFiles) { - const name = file.replace('.mdx', ''); - metaEntries[name] = name; - } - - await fs.writeFile( - path.join(directory, '_meta.json'), - JSON.stringify(metaEntries, null, 2) - ); - - console.log(`Created _meta.json in ${path.relative(process.cwd(), directory)}`); - } catch (error) { - console.error(`Error creating _meta.json: ${error.message}`); - } -} - -async function main() { - try { - console.log('Starting copy of evolution documentation...'); - - // Remove existing target directory - try { - await fs.rm(targetDir, { recursive: true, force: true }); - console.log(`Removing existing directory: ${targetDir}`); - } catch (error) { - // Directory might not exist, that's ok - } - - // Copy source to target - await copyDirectory(sourceDir, targetDir); - - // Create _meta.json for navigation - await createMetaJson(targetDir); - - console.log('Documentation copy completed successfully!'); - } catch (error) { - console.error('Error copying documentation:', error.message); - process.exit(1); - } -} - -main(); diff --git a/docs/scripts/copy-evolution-docs.ts b/docs/scripts/copy-evolution-docs.ts new file mode 100644 index 00000000..39f63218 --- /dev/null +++ b/docs/scripts/copy-evolution-docs.ts @@ -0,0 +1,117 @@ +#!/usr/bin/env node + +import * as fs from "fs/promises" +import * as path from "path" + +const sourceDir = path.resolve(process.cwd(), "../packages/evolution/docs/modules") +const targetDir = path.resolve(process.cwd(), "./pages/reference/modules") + +async function ensureDirectoryExists(dir) { + try { + await fs.access(dir) + } catch (error) { + await fs.mkdir(dir, { recursive: true }) + console.log(`Created directory: ${dir}`) + } +} + +function convertMdToMdx(content) { + // Convert HTML class attributes to className for React/MDX compatibility + content = content.replace(/class="/g, 'className="') + + // Just copy the files and rename them - no complex processing + return content +} + +async function copyFile(src, dest) { + try { + const content = await fs.readFile(src, "utf8") + const convertedContent = convertMdToMdx(content) + await fs.writeFile(dest, convertedContent) + console.log(`Copied: ${path.relative(process.cwd(), src)} → ${path.relative(process.cwd(), dest)}`) + } catch (error) { + console.error(`Error copying ${src} to ${dest}: ${error.message}`) + throw error + } +} + +async function copyDirectory(source, target) { + await ensureDirectoryExists(target) + + const entries = await fs.readdir(source, { withFileTypes: true }) + + for (const entry of entries) { + const sourcePath = path.join(source, entry.name) + let targetName = entry.name + + // For .md files, convert to .mdx and remove .ts for cleaner navigation + if (entry.name.endsWith(".md")) { + if (entry.name.endsWith(".ts.md")) { + // Convert Address.ts.md to Address.mdx (remove .ts) + targetName = entry.name.replace(/\.ts\.md$/, ".mdx") + } else { + targetName = entry.name.replace(/\.md$/, ".mdx") + } + } + + const targetPath = path.join(target, targetName) + + if (entry.isDirectory()) { + await copyDirectory(sourcePath, targetPath) + } else { + await copyFile(sourcePath, targetPath) + } + } +} + +async function createMetaJson(directory) { + try { + const entries = await fs.readdir(directory) + const metaEntries = {} + + // Filter only the .mdx files (converted from .md) + const mdxFiles = entries.filter((entry) => entry.endsWith(".mdx") && entry !== "index.mdx") + + // Sort alphabetically + mdxFiles.sort() + + // Create meta entries for each file using simple string format + for (const file of mdxFiles) { + const name = file.replace(".mdx", "") + metaEntries[name] = name + } + + await fs.writeFile(path.join(directory, "_meta.json"), JSON.stringify(metaEntries, null, 2)) + + console.log(`Created _meta.json in ${path.relative(process.cwd(), directory)}`) + } catch (error) { + console.error(`Error creating _meta.json: ${error.message}`) + } +} + +async function main() { + try { + console.log("Starting copy of evolution documentation...") + + // Remove existing target directory + try { + await fs.rm(targetDir, { recursive: true, force: true }) + console.log(`Removing existing directory: ${targetDir}`) + } catch (error) { + // Directory might not exist, that's ok + } + + // Copy source to target + await copyDirectory(sourceDir, targetDir) + + // Create _meta.json for navigation + await createMetaJson(targetDir) + + console.log("Documentation copy completed successfully!") + } catch (error) { + console.error("Error copying documentation:", error.message) + process.exit(1) + } +} + +main() diff --git a/docs/scripts/generate-getting-started.ts b/docs/scripts/generate-getting-started.ts new file mode 100644 index 00000000..79cc516e --- /dev/null +++ b/docs/scripts/generate-getting-started.ts @@ -0,0 +1,312 @@ +#!/usr/bin/env tsx + +// Snippet injector: replaces placeholders in MDX with code regions from TS files. +// Usage: tsx scripts/generate-getting-started.ts (run from docs package) +// Placeholders in MDX: +// +// ... auto-filled ... +// +// Regions in TS examples use VS Code style region markers: +// // #region address-example +// // code +// // #endregion address-example + +import * as fs from "fs/promises" +import * as path from "path" +import { fileURLToPath } from "url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +// docs/ directory +const DOCS_ROOT = path.resolve(__dirname, "..") +const EXAMPLES_DIR = path.resolve(DOCS_ROOT, "examples") +const PAGES_DIR = path.resolve(DOCS_ROOT, "pages/getting-started") + +interface SnippetAttributes { + file?: string + region?: string + lang?: string + stripImports?: string +} + +/** Parse attributes from a BEGIN:snippet comment token (supports HTML and MDX comments) */ +function parseAttrs(token: string): SnippetAttributes { + const attrs: SnippetAttributes = {} + let raw: string | null = null + if (token.includes("|$)/) + raw = m?.[1] ?? null + } else if (token.includes("{/*")) { + const m = token.match(/BEGIN:snippet\s+(.+?)(?:\*\/\}|$)/) + raw = m?.[1] ?? null + } + if (!raw) return attrs + const parts = raw.match(/(\w+)=([^\s]+)/g) || [] + for (const part of parts) { + const [k, v] = part.split("=") + attrs[k as keyof SnippetAttributes] = v.replace(/^"|^'|"$|'$/g, "") + } + return attrs +} + +function escapeRegex(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") +} + +/** Extract a region from a TS file using // #region markers */ +async function extractRegion(filePath: string, region: string): Promise { + const content = await fs.readFile(filePath, "utf8") + const escaped = escapeRegex(region) + const startRe = new RegExp(`^\\s*\/\/\\s*#region\\s+${escaped}.*$`, "m") + const startMatch = startRe.exec(content) + if (!startMatch) throw new Error(`Region '${region}' not found in ${filePath}`) + const afterStartLineIdx = content.indexOf("\n", startMatch.index) + const codeStart = afterStartLineIdx === -1 ? content.length : afterStartLineIdx + 1 + + const endRe = new RegExp(`^\\s*\/\/\\s*#endregion\\s+${escaped}.*$`, "m") + const endSlice = content.slice(codeStart) + const endMatch = endRe.exec(endSlice) + if (!endMatch) throw new Error(`End of region '${region}' not found in ${filePath}`) + const codeEnd = codeStart + endMatch.index + return content.slice(codeStart, codeEnd) +} + +/** Optionally strip import lines that include any of the tokens (comma-separated) */ +function stripImportLines(code: string, tokensCsv?: string): string { + if (!tokensCsv) return code + const tokens = tokensCsv + .split(",") + .map((s: string) => s.trim()) + .filter(Boolean) + const lines = code.split("\n") + const filtered = lines.filter((ln: string) => { + if (!ln.trim().startsWith("import")) return true + return !tokens.some((t: string) => ln.includes(t)) + }) + return filtered.join("\n") +} + +interface MatchInfo { + type: "html" | "mdx" + endRe: RegExp + index: number + token: string +} + +/** Replace all snippet blocks in an MDX file */ +async function processMdxFile(mdxPath: string): Promise { + let mdx = await fs.readFile(mdxPath, "utf8") + // Support both HTML comments and MDX comments for snippet markers + const beginPatterns = [ + { type: "html" as const, re: //g, endRe: // }, + { type: "mdx" as const, re: /\{\/\*\s*BEGIN:snippet[^*}]*\*\/\}/g, endRe: /\{\/\*\s*END:snippet\s*\*\/\}/ } + ] + const matches: MatchInfo[] = [] + for (const p of beginPatterns) { + for (const m of mdx.matchAll(p.re)) { + matches.push({ type: p.type, endRe: p.endRe, index: m.index ?? 0, token: m[0] }) + } + } + if (matches.length === 0) return false + // Process in source order + matches.sort((a, b) => a.index - b.index) + + let offset = 0 + for (const m of matches) { + const beginIdx = m.index + offset + // Find end marker after this begin + const afterBegin = mdx.slice(beginIdx) + const endMatch = afterBegin.match(m.endRe) + if (!endMatch) throw new Error(`Missing END:snippet in ${mdxPath}`) + const endIdx = beginIdx + (endMatch.index ?? 0) + endMatch[0].length + + // Extract attributes + const beginToken = m.token + const attrs = parseAttrs(beginToken) + if (!attrs.file || !attrs.region) { + throw new Error(`BEGIN:snippet missing file or region in ${mdxPath}: ${beginToken}`) + } + const lang = attrs.lang || "ts" + const stripImports = attrs.stripImports + + const examplePath = path.resolve(EXAMPLES_DIR, attrs.file) + const codeRaw = await extractRegion(examplePath, attrs.region) + const code = stripImportLines(codeRaw.trimEnd(), stripImports) + + const fenced = `\n\n\`\`\`${lang}\n${code}\n\`\`\`\n\n` + + // Replace everything between begin and end with the begin + fenced + end + const endToken = m.type === "mdx" ? "{/* END:snippet */}" : "" + const newBlock = `${beginToken}${fenced}${endToken}` + mdx = mdx.slice(0, beginIdx) + newBlock + mdx.slice(endIdx) + offset = offset + (newBlock.length - (endIdx - beginIdx)) + } + + await fs.writeFile(mdxPath, mdx) + return true +} + +async function main() { + // 1) Replace inline snippet blocks in existing MDX files + const pages = await fs.readdir(PAGES_DIR) + const mdxFiles = pages.filter((f) => f.endsWith(".mdx")) + let changed = 0 + for (const f of mdxFiles) { + const p = path.join(PAGES_DIR, f) + const did = await processMdxFile(p).catch((err) => { + console.error(`Error processing ${f}:`, err.message) + process.exitCode = 1 + return false + }) + if (did) changed++ + } + + // 2) Auto-generate grouped example pages if folders exist under examples/ + const groups = await listGroups(EXAMPLES_DIR) + for (const g of groups) { + const created = await generateGroupPages(g).catch((err) => { + console.error(`Error generating group '${g}':`, err.message) + process.exitCode = 1 + return 0 + }) + if (created > 0) changed += created + } + + console.log(`Snippet generation complete. Updated ${changed} file(s).`) +} + +main() + +// Utilities for group generation +async function listGroups(baseDir: string): Promise { + try { + const entries = await fs.readdir(baseDir, { withFileTypes: true }) + return entries.filter((e) => e.isDirectory()).map((e) => e.name) + } catch { + return [] + } +} + +function toTitleCase(s: string): string { + return s + .replace(/[-_]+/g, " ") + .replace(/\s+/g, " ") + .trim() + .replace(/\b\w/g, (m: string) => m.toUpperCase()) +} + +function toSlug(s: string): string { + return s + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/(^-|-$)/g, "") +} + +async function readText(p: string): Promise { + return fs.readFile(p, "utf8") +} + +function extractDirective(content: string, name: string): string | null { + // Looks for // @name: value or /* @name: value */ on first ~10 lines + const lines = content.split(/\r?\n/).slice(0, 10) + for (const line of lines) { + const m1 = line.match(new RegExp(`^\\s*\\/\\/\\s*@${name}:\\s*(.+)$`)) + if (m1) return m1[1].trim() + + const m2 = line.match(new RegExp(`\\/\\*\\s*@${name}:\\s*([^*]+)\\*\\/`)) + if (m2) return m2[1].trim() + } + return null +} + +async function getExampleCodeForFile(filePath: string): Promise { + const content = await readText(filePath) + // Prefer a region named 'main' for clarity; else include entire file + try { + return await extractRegion(filePath, "main") + } catch { + return content.trimEnd() + } +} + +interface LinkInfo { + title: string + slug: string +} + +async function generateGroupPages(groupName: string): Promise { + const groupDir = path.join(EXAMPLES_DIR, groupName) + let files: string[] + try { + files = (await fs.readdir(groupDir)).filter((f: string) => f.endsWith(".ts") || f.endsWith(".tsx")) + } catch { + return 0 + } + if (files.length === 0) return 0 + + const outDir = path.join(PAGES_DIR, groupName) + + // Clean up existing directory to ensure fresh generation + try { + await fs.rm(outDir, { recursive: true, force: true }) + console.log(` Cleaned existing directory: ${outDir}`) + } catch { + // Directory might not exist, that's fine + } + + await fs.mkdir(outDir, { recursive: true }) + + // Generate individual pages + let created = 0 + const links: LinkInfo[] = [] + for (const f of files) { + const abs = path.join(groupDir, f) + const content = await readText(abs) + const code = await getExampleCodeForFile(abs) + const title = extractDirective(content, "title") || toTitleCase(f.replace(/\.(ts|tsx)$/i, "")) + const desc = extractDirective(content, "description") + const slug = toSlug(f.replace(/\.(ts|tsx)$/i, "")) + + const mdx = [`# ${title}`, "", ...(desc ? [desc, ""] : []), "```typescript", code, "```", ""].join("\n") + + const outFile = path.join(outDir, `${slug}.mdx`) + let shouldWrite = true + try { + const existing = await fs.readFile(outFile, "utf8") + if (existing === mdx) shouldWrite = false + } catch {} + if (shouldWrite) { + await fs.writeFile(outFile, mdx) + created++ + } + links.push({ title, slug }) + } + + // Generate or refresh index.mdx with a simple list of links + const indexTitle = `${toTitleCase(groupName)} Examples` + const indexLines = [ + `# ${indexTitle}`, + "", + ...links.map( + (l: LinkInfo) => + `- [${l.title}](/${path.relative(path.join(DOCS_ROOT, "pages"), path.join(outDir, l.slug)).replace(/\\/g, "/")})` + ), + "" + ] + const indexPath = path.join(outDir, "index.mdx") + const indexContent = indexLines.join("\n") + let wroteIndex = 0 + try { + const existing = await fs.readFile(indexPath, "utf8") + if (existing !== indexContent) { + await fs.writeFile(indexPath, indexContent) + wroteIndex = 1 + } + } catch { + await fs.writeFile(indexPath, indexContent) + wroteIndex = 1 + } + + return created + wroteIndex +} diff --git a/docs/scripts/run-examples-and-generate.sh b/docs/scripts/run-examples-and-generate.sh new file mode 100755 index 00000000..0a345b79 --- /dev/null +++ b/docs/scripts/run-examples-and-generate.sh @@ -0,0 +1,131 @@ +#!/bin/bash + +# Script to run all TypeScript examples and generate MDX documentation +# This ensures examples are working before generating documentation + +set -e # Exit on any error + +DOCS_DIR="$(dirname "$(dirname "$0")")" +EXAMPLES_DIR="$DOCS_DIR/examples" +SCRIPTS_DIR="$DOCS_DIR/scripts" + +echo "🚀 Running Evolution SDK Documentation Generator" +echo "===============================================" + +# Function to run a single TypeScript file +run_example() { + local file="$1" + local relative_path="${file#$EXAMPLES_DIR/}" + + echo "📄 Running: $relative_path" + + # Use pnpm exec to ensure we have access to workspace dependencies + if ! pnpm exec tsx "$file"; then + echo "❌ Failed to run: $relative_path" + return 1 + else + echo "✅ Success: $relative_path" + return 0 + fi +} + +# Function to run examples in a directory +run_examples_in_dir() { + local dir="$1" + local failures=0 + + if [[ -d "$dir" ]]; then + echo "" + echo "📁 Running examples in: ${dir#$EXAMPLES_DIR/}" + echo "-------------------------------------------" + + # Find all .ts files in the directory + while IFS= read -r -d '' file; do + if ! run_example "$file"; then + ((failures++)) + fi + done < <(find "$dir" -name "*.ts" -type f -print0 | sort -z) + + if [[ $failures -gt 0 ]]; then + echo "❌ $failures example(s) failed in ${dir#$EXAMPLES_DIR/}" + return 1 + else + echo "✅ All examples passed in ${dir#$EXAMPLES_DIR/}" + return 0 + fi + fi +} + +# Main execution +main() { + local total_failures=0 + + # Change to docs directory + cd "$DOCS_DIR" + + # Run individual example files in root examples directory + if [[ -d "$EXAMPLES_DIR" ]]; then + echo "" + echo "📄 Running individual examples..." + echo "--------------------------------" + + local found_individual=0 + while IFS= read -r -d '' file; do + found_individual=1 + if ! run_example "$file"; then + ((total_failures++)) + fi + done < <(find "$EXAMPLES_DIR" -maxdepth 1 -name "*.ts" -type f -print0 | sort -z) + + if [[ $found_individual -eq 0 ]]; then + echo "â„šī¸ No individual examples found" + fi + fi + + # Run examples in subdirectories (like data/, address/, etc.) + if [[ -d "$EXAMPLES_DIR" ]]; then + local found_dirs=0 + while IFS= read -r -d '' dir; do + found_dirs=1 + if ! run_examples_in_dir "$dir"; then + ((total_failures++)) + fi + done < <(find "$EXAMPLES_DIR" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) + + if [[ $found_dirs -eq 0 ]]; then + echo "â„šī¸ No example directories found" + fi + fi + + echo "" + echo "📊 Test Summary" + echo "===============" + + if [[ $total_failures -eq 0 ]]; then + echo "✅ All examples ran successfully!" + echo "" + echo "🔄 Generating MDX documentation..." + echo "--------------------------------" + + # Run the snippet generation script + if pnpm exec tsx "scripts/generate-getting-started.ts"; then + echo "✅ MDX documentation generated successfully!" + echo "" + echo "🎉 Documentation build complete!" + echo "You can now run 'pnpm run dev' to view the documentation." + else + echo "❌ Failed to generate MDX documentation" + exit 1 + fi + else + echo "❌ $total_failures example(s) failed" + echo "Please fix the failing examples before generating documentation." + exit 1 + fi +} + +# Handle script interruption +trap 'echo ""; echo "âš ī¸ Script interrupted"; exit 130' INT + +# Run main function +main "$@" diff --git a/docs/test/examples.test.ts b/docs/test/examples.test.ts deleted file mode 100644 index 9732cd03..00000000 --- a/docs/test/examples.test.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { describe, it, expect } from "@effect/vitest" -import * as Address from "@evolution-sdk/evolution/Address" -import * as PaymentAddress from "@evolution-sdk/evolution/PaymentAddress" -import * as RewardAddress from "@evolution-sdk/evolution/RewardAddress" -import * as Data from "@evolution-sdk/evolution/Data" - -/** - * Test suite to verify all examples from documentation work correctly - */ -describe("Documentation Examples Verification", () => { - describe("Quick Start Examples", () => { - it("should validate payment and reward addresses correctly", () => { - // Sample addresses from quick-start.mdx - const mainnetBaseAddress = - "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x" - const mainnetRewardAddress = "stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw" - const testnetBaseAddress = - "addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae" - const testnetRewardAddress = "stake_test1uqehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gssrtvn" - - // Validate payment addresses - expect(PaymentAddress.isPaymentAddress(mainnetBaseAddress)).toBe(true) - expect(PaymentAddress.isPaymentAddress(testnetBaseAddress)).toBe(true) - expect(PaymentAddress.isPaymentAddress(mainnetRewardAddress)).toBe(false) - expect(PaymentAddress.isPaymentAddress(testnetRewardAddress)).toBe(false) - - // Validate reward addresses - expect(RewardAddress.isRewardAddress(mainnetRewardAddress)).toBe(true) - expect(RewardAddress.isRewardAddress(testnetRewardAddress)).toBe(true) - expect(RewardAddress.isRewardAddress(mainnetBaseAddress)).toBe(false) - expect(RewardAddress.isRewardAddress(testnetBaseAddress)).toBe(false) - }) - - it("should perform address format conversion correctly", () => { - // Example from quick-start.mdx - const hexAddress = "60ba1d6b6283c219a0530e3682c316215d55819cf97bbf26552c6f8530" - const expectedBech32 = "addr_test1vzap66mzs0ppngznpcmg9scky9w4tqvul9am7fj493hc2vq4ry02m" - - // Decode from hex - const address = Address.Codec.Decode.hex(hexAddress) - expect(address).toBeDefined() - - // Encode to bech32 - const actualBech32 = Address.Codec.Encode.bech32(address) - expect(actualBech32).toBe(expectedBech32) - - // Round-trip conversion - const backToHex = Address.Codec.Encode.hex(address) - expect(backToHex).toBe(hexAddress) - }) - - it("should validate different address types", () => { - // Address types from quick-start.mdx - const baseAddress = - "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x" - const enterpriseAddress = "addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer66hrl8" - const pointerAddress = "addr1gx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5pnz75xxcrzqf96k" - const rewardAddress = "stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw" - - // All should be valid addresses for their respective types - expect(PaymentAddress.isPaymentAddress(baseAddress)).toBe(true) - expect(PaymentAddress.isPaymentAddress(enterpriseAddress)).toBe(true) - expect(PaymentAddress.isPaymentAddress(pointerAddress)).toBe(true) - expect(RewardAddress.isRewardAddress(rewardAddress)).toBe(true) - }) - - it("should validate data types correctly", () => { - // Examples from quick-start.mdx - const validHex = "deadbeef" - const isValidBytes = Data.isBytes(validHex) - expect(isValidBytes).toBe(true) - - const invalidHex = "not-hex" - const isInvalidBytes = Data.isBytes(invalidHex) - expect(isInvalidBytes).toBe(false) - }) - }) - - describe("Examples Page Verification", () => { - it("should validate all mainnet and testnet addresses", () => { - const addresses = { - // Mainnet addresses - mainnet: { - base: "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x", - enterprise: "addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer66hrl8", - pointer: "addr1gx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5pnz75xxcrzqf96k", - reward: "stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw" - }, - // Testnet addresses - testnet: { - base: "addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae", - enterprise: "addr_test1vz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5pnz75xxcrdw5vky", - pointer: "addr_test1gz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5pnz75xxcrdw5vky", - reward: "stake_test1uqehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gssrtvn" - } - } - - // Test payment addresses - const paymentAddresses = [ - addresses.mainnet.base, - addresses.mainnet.enterprise, - addresses.mainnet.pointer, - addresses.testnet.base, - addresses.testnet.enterprise, - addresses.testnet.pointer - ] - - paymentAddresses.forEach((address) => { - expect(PaymentAddress.isPaymentAddress(address)).toBe(true) - }) - - // Test reward addresses - const rewardAddresses = [addresses.mainnet.reward, addresses.testnet.reward] - - rewardAddresses.forEach((address) => { - expect(RewardAddress.isRewardAddress(address)).toBe(true) - }) - }) - - it("should validate hex addresses from examples", () => { - const validHexAddresses = [ - "019493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e337b62cfff6403a06a3acbc34f8c46003c69fe79a3628cefa9c47251", - "60ba1d6b6283c219a0530e3682c316215d55819cf97bbf26552c6f8530" - ] - - validHexAddresses.forEach((hex) => { - // Should decode without error - const address = Address.Codec.Decode.hex(hex) - expect(address).toBeDefined() - - // Should encode back to same hex - const backToHex = Address.Codec.Encode.hex(address) - expect(backToHex).toBe(hex) - - // Should encode to valid bech32 - const bech32 = Address.Codec.Encode.bech32(address) - expect(typeof bech32).toBe("string") - expect(bech32.length).toBeGreaterThan(0) - }) - }) - - it("should validate data type examples", () => { - // Valid hex examples from documentation - const validHexCases = ["deadbeef", "cafe0123", "abcdef0123456789", "00", "ff"] - - validHexCases.forEach((hex) => { - expect(Data.isBytes(hex)).toBe(true) - }) - - // Invalid hex examples from documentation - const invalidHexCases = [ - "not-hex", - "xyz", - "123g", - "deadbeef ", // trailing space - " deadbeef", // leading space - "0x123456" // hex prefix not allowed - ] - - invalidHexCases.forEach((hex) => { - expect(Data.isBytes(hex)).toBe(false) - }) - }) - - it("should handle batch validation correctly", () => { - // Mixed collection from examples - const testAddresses = [ - "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x", - "stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw", - "not-an-address", - "addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse68faae", - "stake_test1uqehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gssrtvn" - ] - - const results = testAddresses.map((address) => { - const isPayment = PaymentAddress.isPaymentAddress(address) - const isReward = RewardAddress.isRewardAddress(address) - - let type = "invalid" - if (isPayment) type = "payment" - else if (isReward) type = "reward" - - return { - address, - type, - valid: isPayment || isReward - } - }) - - // Verify expected results - expect(results[0].type).toBe("payment") - expect(results[1].type).toBe("reward") - expect(results[2].type).toBe("invalid") - expect(results[3].type).toBe("payment") - expect(results[4].type).toBe("reward") - }) - }) -}) diff --git a/package.json b/package.json index 2c901001..08e4405d 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "docgen": "turbo docgen", "docs:dev": "pnpm --filter @evolution-sdk/docs dev", "docs:build": "pnpm --filter @evolution-sdk/docs build", - "docs:start": "pnpm --filter @evolution-sdk/docs start", - "docs:verify": "pnpm --filter @evolution-sdk/docs verify", + "docs:start": "pnpm --filter @evolution-sdk/docs start", "verify": "turbo verify", "circular": "madge --circular --extensions ts packages/*/src/index.ts", "changeset": "changeset", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5f655a6..f7649052 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,6 +111,9 @@ importers: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) devDependencies: + '@evolution-sdk/evolution': + specifier: workspace:* + version: link:../packages/evolution '@types/node': specifier: ^20.0.0 version: 20.19.9 @@ -120,6 +123,12 @@ importers: '@types/react-dom': specifier: ^18.2.0 version: 18.3.7(@types/react@18.3.23) + effect: + specifier: ^3.17.3 + version: 3.17.3 + tsx: + specifier: ^4.20.3 + version: 4.20.3 typescript: specifier: ^5.0.0 version: 5.8.3 From 7ed828956db927e9604677fedf6d798434771c3a Mon Sep 17 00:00:00 2001 From: Jonathan <57915702+solidsnakedev@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:42:49 -0400 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/scripts/copy-evolution-docs.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/scripts/copy-evolution-docs.ts b/docs/scripts/copy-evolution-docs.ts index 39f63218..20f94f72 100644 --- a/docs/scripts/copy-evolution-docs.ts +++ b/docs/scripts/copy-evolution-docs.ts @@ -6,7 +6,7 @@ import * as path from "path" const sourceDir = path.resolve(process.cwd(), "../packages/evolution/docs/modules") const targetDir = path.resolve(process.cwd(), "./pages/reference/modules") -async function ensureDirectoryExists(dir) { +async function ensureDirectoryExists(dir: string) { try { await fs.access(dir) } catch (error) { @@ -15,7 +15,7 @@ async function ensureDirectoryExists(dir) { } } -function convertMdToMdx(content) { +function convertMdToMdx(content: string) { // Convert HTML class attributes to className for React/MDX compatibility content = content.replace(/class="/g, 'className="') @@ -23,7 +23,7 @@ function convertMdToMdx(content) { return content } -async function copyFile(src, dest) { +async function copyFile(src: string, dest: string) { try { const content = await fs.readFile(src, "utf8") const convertedContent = convertMdToMdx(content) @@ -35,7 +35,7 @@ async function copyFile(src, dest) { } } -async function copyDirectory(source, target) { +async function copyDirectory(source: string, target: string) { await ensureDirectoryExists(target) const entries = await fs.readdir(source, { withFileTypes: true }) @@ -64,7 +64,7 @@ async function copyDirectory(source, target) { } } -async function createMetaJson(directory) { +async function createMetaJson(directory: string) { try { const entries = await fs.readdir(directory) const metaEntries = {} From 7ea5cf7b78e6e70e8c6f663b24e550e6e189695d Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Thu, 21 Aug 2025 15:49:19 -0600 Subject: [PATCH 3/3] fix: script and update deps ci --- .github/workflows/update-dependencies.yml | 4 ++++ docs/scripts/copy-evolution-docs.ts | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index 038d17aa..eed69ad1 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -1,5 +1,9 @@ name: Update Dependencies +permissions: + contents: write + pull-requests: write + on: schedule: # Run every Monday at 9:00 AM UTC diff --git a/docs/scripts/copy-evolution-docs.ts b/docs/scripts/copy-evolution-docs.ts index 20f94f72..13263760 100644 --- a/docs/scripts/copy-evolution-docs.ts +++ b/docs/scripts/copy-evolution-docs.ts @@ -30,7 +30,7 @@ async function copyFile(src: string, dest: string) { await fs.writeFile(dest, convertedContent) console.log(`Copied: ${path.relative(process.cwd(), src)} → ${path.relative(process.cwd(), dest)}`) } catch (error) { - console.error(`Error copying ${src} to ${dest}: ${error.message}`) + console.error(`Error copying ${src} to ${dest}: ${(error as Error).message}`) throw error } } @@ -67,7 +67,7 @@ async function copyDirectory(source: string, target: string) { async function createMetaJson(directory: string) { try { const entries = await fs.readdir(directory) - const metaEntries = {} + const metaEntries: Record = {} // Filter only the .mdx files (converted from .md) const mdxFiles = entries.filter((entry) => entry.endsWith(".mdx") && entry !== "index.mdx") @@ -85,7 +85,7 @@ async function createMetaJson(directory: string) { console.log(`Created _meta.json in ${path.relative(process.cwd(), directory)}`) } catch (error) { - console.error(`Error creating _meta.json: ${error.message}`) + console.error(`Error creating _meta.json: ${(error as Error).message}`) } } @@ -109,7 +109,7 @@ async function main() { console.log("Documentation copy completed successfully!") } catch (error) { - console.error("Error copying documentation:", error.message) + console.error("Error copying documentation:", (error as Error).message) process.exit(1) } }