diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e743988..73ace3d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Rust uses: actions-rust-lang/setup-rust-toolchain@v1 @@ -20,9 +20,9 @@ jobs: toolchain: stable - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: "20" + node-version: lts/* - name: Build Rust workspace run: cargo build --workspace @@ -49,3 +49,11 @@ jobs: - name: Run integration tests working-directory: tests run: npm test + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: tests/playwright-report/ + retention-days: 14 diff --git a/.prettierrc b/.prettierrc index de753c5..d66436a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ { - "printWidth": 100 + "printWidth": 100, + "plugins": ["prettier-plugin-jinja-template"] } diff --git a/Cargo.toml b/Cargo.toml index 789c772..40f5573 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ hmac = "0.12" log = "0.4" rand = "0.8" sha2 = "0.10" +url = "2.5" minijinja = { version = "2.12", features = ["loader"] } minijinja-autoreload = "2.12.0" minijinja-embed = "2.12.0" diff --git a/crates/observation-tools-client/index.d.ts b/crates/observation-tools-client/index.d.ts index 7125b20..dadbaba 100644 --- a/crates/observation-tools-client/index.d.ts +++ b/crates/observation-tools-client/index.d.ts @@ -2,34 +2,34 @@ /* eslint-disable */ /** Client for observation-tools */ export declare class Client { - beginExecution(name: string): ExecutionHandle; + beginExecution(name: string): ExecutionHandle /** * Begin a new execution with a specific ID (for testing) * * This allows tests to create an execution with a known ID, enabling * navigation to the execution URL before the execution is uploaded. */ - beginExecutionWithId(id: string, name: string): ExecutionHandle; + beginExecutionWithId(id: string, name: string): ExecutionHandle } /** Builder for Client */ export declare class ClientBuilder { /** Create a new client builder */ - constructor(); + constructor() /** Set the base URL for the server */ - setBaseUrl(url: string): void; + setBaseUrl(url: string): void /** Set the API key for authentication */ - setApiKey(apiKey: string): void; + setApiKey(apiKey: string): void /** Build the client */ - build(): Client; + build(): Client } /** Handle to an execution that can be used to send observations */ export declare class ExecutionHandle { /** Get the execution ID as a string */ - get idString(): string; + get idString(): string /** Get the URL to the execution page */ - get url(): string; + get url(): string /** * Create and send an observation * @@ -41,14 +41,23 @@ export declare class ExecutionHandle { * * `source_line` - Optional source line number * * `metadata` - Optional metadata as an array of [key, value] pairs */ - observe( - name: string, - payloadJson: string, - labels?: Array | undefined | null, - sourceFile?: string | undefined | null, - sourceLine?: number | undefined | null, - metadata?: Array> | undefined | null, - ): string; + observe(name: string, payloadJson: string, labels?: Array | undefined | null, sourceFile?: string | undefined | null, sourceLine?: number | undefined | null, metadata?: Array> | undefined | null): string + /** + * Create and send an observation with a specific ID (for testing) + * + * This allows tests to create an observation with a known ID, enabling + * navigation to the observation URL before the observation is uploaded. + * + * # Arguments + * * `id` - The observation ID to use + * * `name` - The name of the observation + * * `payload_json` - The data to observe as a JSON string + * * `labels` - Optional array of labels for categorization + * * `source_file` - Optional source file path + * * `source_line` - Optional source line number + * * `metadata` - Optional metadata as an array of [key, value] pairs + */ + observeWithId(id: string, name: string, payloadJson: string, labels?: Array | undefined | null, sourceFile?: string | undefined | null, sourceLine?: number | undefined | null, metadata?: Array> | undefined | null): string } /** @@ -59,19 +68,19 @@ export declare class ExecutionHandle { */ export declare class ObservationBuilder { /** Create a new observation builder with the given name */ - constructor(name: string); + constructor(name: string) /** Add a label to the observation */ - label(label: string): this; + label(label: string): this /** Add metadata to the observation */ - metadata(key: string, value: string): this; + metadata(key: string, value: string): this /** Set the source info for the observation */ - source(file: string, line: number): this; + source(file: string, line: number): this /** Set the payload as JSON data */ - jsonPayload(jsonString: string): ObservationBuilderWithPayload; + jsonPayload(jsonString: string): ObservationBuilderWithPayload /** Set the payload with custom data and MIME type */ - rawPayload(data: string, mimeType: string): ObservationBuilderWithPayload; + rawPayload(data: string, mimeType: string): ObservationBuilderWithPayload /** Set the payload as markdown content */ - markdownPayload(content: string): ObservationBuilderWithPayload; + markdownPayload(content: string): ObservationBuilderWithPayload } /** @@ -90,15 +99,15 @@ export declare class ObservationBuilderWithPayload { * * If sending fails, returns a stub that will fail on `wait_for_upload()`. */ - send(execution: ExecutionHandle): SendObservation; + send(execution: ExecutionHandle): SendObservation } export declare class ObservationHandle { - get url(): string; + get url(): string } export declare class SendObservation { - handle(): ObservationHandle; + handle(): ObservationHandle } /** @@ -107,4 +116,13 @@ export declare class SendObservation { * This allows tests to generate an execution ID before creating the execution, * enabling navigation to the execution URL before the execution is uploaded. */ -export declare function generateExecutionId(): string; +export declare function generateExecutionId(): string + +/** + * Generate a new observation ID (for testing) + * + * This allows tests to generate an observation ID before creating the + * observation, enabling navigation to the observation URL before the + * observation is uploaded. + */ +export declare function generateObservationId(): string diff --git a/crates/observation-tools-client/index.js b/crates/observation-tools-client/index.js index aea348d..1aab1d0 100644 --- a/crates/observation-tools-client/index.js +++ b/crates/observation-tools-client/index.js @@ -4,742 +4,553 @@ /* auto-generated by NAPI-RS */ const { readFileSync } = require('node:fs') -let nativeBinding = null; -const loadErrors = []; +let nativeBinding = null +const loadErrors = [] const isMusl = () => { - let musl = false; - if (process.platform === "linux") { - musl = isMuslFromFilesystem(); + let musl = false + if (process.platform === 'linux') { + musl = isMuslFromFilesystem() if (musl === null) { - musl = isMuslFromReport(); + musl = isMuslFromReport() } if (musl === null) { - musl = isMuslFromChildProcess(); + musl = isMuslFromChildProcess() } } - return musl; -}; + return musl +} -const isFileMusl = (f) => f.includes("libc.musl-") || f.includes("ld-musl-"); +const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-') const isMuslFromFilesystem = () => { try { - return readFileSync("/usr/bin/ldd", "utf-8").includes("musl"); + return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl') } catch { - return null; + return null } -}; +} const isMuslFromReport = () => { - let report = null; - if (typeof process.report?.getReport === "function") { - process.report.excludeNetwork = true; - report = process.report.getReport(); + let report = null + if (typeof process.report?.getReport === 'function') { + process.report.excludeNetwork = true + report = process.report.getReport() } if (!report) { - return null; + return null } if (report.header && report.header.glibcVersionRuntime) { - return false; + return false } if (Array.isArray(report.sharedObjects)) { if (report.sharedObjects.some(isFileMusl)) { - return true; + return true } } - return false; -}; + return false +} const isMuslFromChildProcess = () => { try { - return require("child_process") - .execSync("ldd --version", { encoding: "utf8" }) - .includes("musl"); + return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl') } catch (e) { // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false - return false; + return false } -}; +} function requireNative() { if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { try { return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); } catch (err) { - loadErrors.push(err); + loadErrors.push(err) } - } else if (process.platform === "android") { - if (process.arch === "arm64") { + } else if (process.platform === 'android') { + if (process.arch === 'arm64') { try { - return require("./observation-tools-client.android-arm64.node"); + return require('./observation-tools-client.android-arm64.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-android-arm64"); - const bindingPackageVersion = - require("@observation-tools/client-android-arm64/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-android-arm64') + const bindingPackageVersion = require('@observation-tools/client-android-arm64/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } - } else if (process.arch === "arm") { + } else if (process.arch === 'arm') { try { - return require("./observation-tools-client.android-arm-eabi.node"); + return require('./observation-tools-client.android-arm-eabi.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-android-arm-eabi"); - const bindingPackageVersion = - require("@observation-tools/client-android-arm-eabi/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-android-arm-eabi') + const bindingPackageVersion = require('@observation-tools/client-android-arm-eabi/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { - loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)); + loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) } - } else if (process.platform === "win32") { - if (process.arch === "x64") { - if ( - process.config?.variables?.shlib_suffix === "dll.a" || - process.config?.variables?.node_target_type === "shared_library" - ) { + } else if (process.platform === 'win32') { + if (process.arch === 'x64') { + if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') { try { - return require("./observation-tools-client.win32-x64-gnu.node"); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require("@observation-tools/client-win32-x64-gnu"); - const bindingPackageVersion = - require("@observation-tools/client-win32-x64-gnu/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; - } catch (e) { - loadErrors.push(e); + return require('./observation-tools-client.win32-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('@observation-tools/client-win32-x64-gnu') + const bindingPackageVersion = require('@observation-tools/client-win32-x64-gnu/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } + return binding + } catch (e) { + loadErrors.push(e) + } } else { try { - return require("./observation-tools-client.win32-x64-msvc.node"); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require("@observation-tools/client-win32-x64-msvc"); - const bindingPackageVersion = - require("@observation-tools/client-win32-x64-msvc/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; - } catch (e) { - loadErrors.push(e); + return require('./observation-tools-client.win32-x64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('@observation-tools/client-win32-x64-msvc') + const bindingPackageVersion = require('@observation-tools/client-win32-x64-msvc/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } + return binding + } catch (e) { + loadErrors.push(e) + } } - } else if (process.arch === "ia32") { + } else if (process.arch === 'ia32') { try { - return require("./observation-tools-client.win32-ia32-msvc.node"); + return require('./observation-tools-client.win32-ia32-msvc.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-win32-ia32-msvc"); - const bindingPackageVersion = - require("@observation-tools/client-win32-ia32-msvc/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-win32-ia32-msvc') + const bindingPackageVersion = require('@observation-tools/client-win32-ia32-msvc/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } - } else if (process.arch === "arm64") { + } else if (process.arch === 'arm64') { try { - return require("./observation-tools-client.win32-arm64-msvc.node"); + return require('./observation-tools-client.win32-arm64-msvc.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-win32-arm64-msvc"); - const bindingPackageVersion = - require("@observation-tools/client-win32-arm64-msvc/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-win32-arm64-msvc') + const bindingPackageVersion = require('@observation-tools/client-win32-arm64-msvc/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { - loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)); + loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) } - } else if (process.platform === "darwin") { + } else if (process.platform === 'darwin') { try { - return require("./observation-tools-client.darwin-universal.node"); + return require('./observation-tools-client.darwin-universal.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-darwin-universal"); - const bindingPackageVersion = - require("@observation-tools/client-darwin-universal/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-darwin-universal') + const bindingPackageVersion = require('@observation-tools/client-darwin-universal/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } - if (process.arch === "x64") { + if (process.arch === 'x64') { try { - return require("./observation-tools-client.darwin-x64.node"); + return require('./observation-tools-client.darwin-x64.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-darwin-x64"); - const bindingPackageVersion = - require("@observation-tools/client-darwin-x64/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-darwin-x64') + const bindingPackageVersion = require('@observation-tools/client-darwin-x64/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } - } else if (process.arch === "arm64") { + } else if (process.arch === 'arm64') { try { - return require("./observation-tools-client.darwin-arm64.node"); + return require('./observation-tools-client.darwin-arm64.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-darwin-arm64"); - const bindingPackageVersion = - require("@observation-tools/client-darwin-arm64/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-darwin-arm64') + const bindingPackageVersion = require('@observation-tools/client-darwin-arm64/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { - loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)); + loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) } - } else if (process.platform === "freebsd") { - if (process.arch === "x64") { + } else if (process.platform === 'freebsd') { + if (process.arch === 'x64') { try { - return require("./observation-tools-client.freebsd-x64.node"); + return require('./observation-tools-client.freebsd-x64.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-freebsd-x64"); - const bindingPackageVersion = - require("@observation-tools/client-freebsd-x64/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-freebsd-x64') + const bindingPackageVersion = require('@observation-tools/client-freebsd-x64/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } - } else if (process.arch === "arm64") { + } else if (process.arch === 'arm64') { try { - return require("./observation-tools-client.freebsd-arm64.node"); + return require('./observation-tools-client.freebsd-arm64.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-freebsd-arm64"); - const bindingPackageVersion = - require("@observation-tools/client-freebsd-arm64/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-freebsd-arm64') + const bindingPackageVersion = require('@observation-tools/client-freebsd-arm64/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { - loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)); + loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) } - } else if (process.platform === "linux") { - if (process.arch === "x64") { + } else if (process.platform === 'linux') { + if (process.arch === 'x64') { if (isMusl()) { try { - return require("./observation-tools-client.linux-x64-musl.node"); + return require('./observation-tools-client.linux-x64-musl.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-x64-musl"); - const bindingPackageVersion = - require("@observation-tools/client-linux-x64-musl/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-x64-musl') + const bindingPackageVersion = require('@observation-tools/client-linux-x64-musl/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { try { - return require("./observation-tools-client.linux-x64-gnu.node"); + return require('./observation-tools-client.linux-x64-gnu.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-x64-gnu"); - const bindingPackageVersion = - require("@observation-tools/client-linux-x64-gnu/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-x64-gnu') + const bindingPackageVersion = require('@observation-tools/client-linux-x64-gnu/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } - } else if (process.arch === "arm64") { + } else if (process.arch === 'arm64') { if (isMusl()) { try { - return require("./observation-tools-client.linux-arm64-musl.node"); + return require('./observation-tools-client.linux-arm64-musl.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-arm64-musl"); - const bindingPackageVersion = - require("@observation-tools/client-linux-arm64-musl/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-arm64-musl') + const bindingPackageVersion = require('@observation-tools/client-linux-arm64-musl/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { try { - return require("./observation-tools-client.linux-arm64-gnu.node"); + return require('./observation-tools-client.linux-arm64-gnu.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-arm64-gnu"); - const bindingPackageVersion = - require("@observation-tools/client-linux-arm64-gnu/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-arm64-gnu') + const bindingPackageVersion = require('@observation-tools/client-linux-arm64-gnu/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } - } else if (process.arch === "arm") { + } else if (process.arch === 'arm') { if (isMusl()) { try { - return require("./observation-tools-client.linux-arm-musleabihf.node"); + return require('./observation-tools-client.linux-arm-musleabihf.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-arm-musleabihf"); - const bindingPackageVersion = - require("@observation-tools/client-linux-arm-musleabihf/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-arm-musleabihf') + const bindingPackageVersion = require('@observation-tools/client-linux-arm-musleabihf/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { try { - return require("./observation-tools-client.linux-arm-gnueabihf.node"); + return require('./observation-tools-client.linux-arm-gnueabihf.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-arm-gnueabihf"); - const bindingPackageVersion = - require("@observation-tools/client-linux-arm-gnueabihf/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-arm-gnueabihf') + const bindingPackageVersion = require('@observation-tools/client-linux-arm-gnueabihf/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } - } else if (process.arch === "loong64") { + } else if (process.arch === 'loong64') { if (isMusl()) { try { - return require("./observation-tools-client.linux-loong64-musl.node"); + return require('./observation-tools-client.linux-loong64-musl.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-loong64-musl"); - const bindingPackageVersion = - require("@observation-tools/client-linux-loong64-musl/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-loong64-musl') + const bindingPackageVersion = require('@observation-tools/client-linux-loong64-musl/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { try { - return require("./observation-tools-client.linux-loong64-gnu.node"); + return require('./observation-tools-client.linux-loong64-gnu.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-loong64-gnu"); - const bindingPackageVersion = - require("@observation-tools/client-linux-loong64-gnu/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-loong64-gnu') + const bindingPackageVersion = require('@observation-tools/client-linux-loong64-gnu/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } - } else if (process.arch === "riscv64") { + } else if (process.arch === 'riscv64') { if (isMusl()) { try { - return require("./observation-tools-client.linux-riscv64-musl.node"); + return require('./observation-tools-client.linux-riscv64-musl.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-riscv64-musl"); - const bindingPackageVersion = - require("@observation-tools/client-linux-riscv64-musl/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-riscv64-musl') + const bindingPackageVersion = require('@observation-tools/client-linux-riscv64-musl/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { try { - return require("./observation-tools-client.linux-riscv64-gnu.node"); + return require('./observation-tools-client.linux-riscv64-gnu.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-riscv64-gnu"); - const bindingPackageVersion = - require("@observation-tools/client-linux-riscv64-gnu/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); + const binding = require('@observation-tools/client-linux-riscv64-gnu') + const bindingPackageVersion = require('@observation-tools/client-linux-riscv64-gnu/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } - return binding; + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } - } else if (process.arch === "ppc64") { + } else if (process.arch === 'ppc64') { try { - return require("./observation-tools-client.linux-ppc64-gnu.node"); + return require('./observation-tools-client.linux-ppc64-gnu.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-ppc64-gnu"); - const bindingPackageVersion = - require("@observation-tools/client-linux-ppc64-gnu/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-linux-ppc64-gnu') + const bindingPackageVersion = require('@observation-tools/client-linux-ppc64-gnu/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } - } else if (process.arch === "s390x") { + } else if (process.arch === 's390x') { try { - return require("./observation-tools-client.linux-s390x-gnu.node"); + return require('./observation-tools-client.linux-s390x-gnu.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-linux-s390x-gnu"); - const bindingPackageVersion = - require("@observation-tools/client-linux-s390x-gnu/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-linux-s390x-gnu') + const bindingPackageVersion = require('@observation-tools/client-linux-s390x-gnu/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { - loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)); + loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) } - } else if (process.platform === "openharmony") { - if (process.arch === "arm64") { + } else if (process.platform === 'openharmony') { + if (process.arch === 'arm64') { try { - return require("./observation-tools-client.openharmony-arm64.node"); + return require('./observation-tools-client.openharmony-arm64.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-openharmony-arm64"); - const bindingPackageVersion = - require("@observation-tools/client-openharmony-arm64/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-openharmony-arm64') + const bindingPackageVersion = require('@observation-tools/client-openharmony-arm64/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } - } else if (process.arch === "x64") { + } else if (process.arch === 'x64') { try { - return require("./observation-tools-client.openharmony-x64.node"); + return require('./observation-tools-client.openharmony-x64.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-openharmony-x64"); - const bindingPackageVersion = - require("@observation-tools/client-openharmony-x64/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-openharmony-x64') + const bindingPackageVersion = require('@observation-tools/client-openharmony-x64/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } - } else if (process.arch === "arm") { + } else if (process.arch === 'arm') { try { - return require("./observation-tools-client.openharmony-arm.node"); + return require('./observation-tools-client.openharmony-arm.node') } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } try { - const binding = require("@observation-tools/client-openharmony-arm"); - const bindingPackageVersion = - require("@observation-tools/client-openharmony-arm/package.json").version; - if ( - bindingPackageVersion !== "0.1.0" && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== "0" - ) { - throw new Error( - `Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, - ); - } - return binding; + const binding = require('@observation-tools/client-openharmony-arm') + const bindingPackageVersion = require('@observation-tools/client-openharmony-arm/package.json').version + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { - loadErrors.push(e); + loadErrors.push(e) } } else { - loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`)); + loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`)) } } else { - loadErrors.push( - new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`), - ); + loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) } } -nativeBinding = requireNative(); +nativeBinding = requireNative() if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { - let wasiBinding = null; - let wasiBindingError = null; + let wasiBinding = null + let wasiBindingError = null try { - wasiBinding = require("./observation-tools-client.wasi.cjs"); - nativeBinding = wasiBinding; + wasiBinding = require('./observation-tools-client.wasi.cjs') + nativeBinding = wasiBinding } catch (err) { if (process.env.NAPI_RS_FORCE_WASI) { - wasiBindingError = err; + wasiBindingError = err } } if (!nativeBinding) { try { - wasiBinding = require("@observation-tools/client-wasm32-wasi"); - nativeBinding = wasiBinding; + wasiBinding = require('@observation-tools/client-wasm32-wasi') + nativeBinding = wasiBinding } catch (err) { if (process.env.NAPI_RS_FORCE_WASI) { - wasiBindingError.cause = err; - loadErrors.push(err); + wasiBindingError.cause = err + loadErrors.push(err) } } } - if (process.env.NAPI_RS_FORCE_WASI === "error" && !wasiBinding) { - const error = new Error("WASI binding not found and NAPI_RS_FORCE_WASI is set to error"); - error.cause = wasiBindingError; - throw error; + if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) { + const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error') + error.cause = wasiBindingError + throw error } } @@ -748,24 +559,25 @@ if (!nativeBinding) { throw new Error( `Cannot find native binding. ` + `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + - "Please try `npm i` again after removing both package-lock.json and node_modules directory.", + 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', { cause: loadErrors.reduce((err, cur) => { - cur.cause = err; - return cur; + cur.cause = err + return cur }), }, - ); + ) } - throw new Error(`Failed to load native binding`); + throw new Error(`Failed to load native binding`) } -module.exports = nativeBinding; -module.exports.Client = nativeBinding.Client; -module.exports.ClientBuilder = nativeBinding.ClientBuilder; -module.exports.ExecutionHandle = nativeBinding.ExecutionHandle; -module.exports.ObservationBuilder = nativeBinding.ObservationBuilder; -module.exports.ObservationBuilderWithPayload = nativeBinding.ObservationBuilderWithPayload; -module.exports.ObservationHandle = nativeBinding.ObservationHandle; -module.exports.SendObservation = nativeBinding.SendObservation; -module.exports.generateExecutionId = nativeBinding.generateExecutionId; +module.exports = nativeBinding +module.exports.Client = nativeBinding.Client +module.exports.ClientBuilder = nativeBinding.ClientBuilder +module.exports.ExecutionHandle = nativeBinding.ExecutionHandle +module.exports.ObservationBuilder = nativeBinding.ObservationBuilder +module.exports.ObservationBuilderWithPayload = nativeBinding.ObservationBuilderWithPayload +module.exports.ObservationHandle = nativeBinding.ObservationHandle +module.exports.SendObservation = nativeBinding.SendObservation +module.exports.generateExecutionId = nativeBinding.generateExecutionId +module.exports.generateObservationId = nativeBinding.generateObservationId diff --git a/crates/observation-tools-client/src/client.rs b/crates/observation-tools-client/src/client.rs index 9bc0127..3d32645 100644 --- a/crates/observation-tools-client/src/client.rs +++ b/crates/observation-tools-client/src/client.rs @@ -88,6 +88,17 @@ pub fn generate_execution_id() -> String { observation_tools_shared::models::ExecutionId::new().to_string() } +/// Generate a new observation ID (for testing) +/// +/// This allows tests to generate an observation ID before creating the +/// observation, enabling navigation to the observation URL before the +/// observation is uploaded. +#[napi(js_name = "generateObservationId")] +#[allow(unused)] +pub fn generate_observation_id() -> String { + observation_tools_shared::models::ObservationId::new().to_string() +} + #[napi] impl Client { #[napi(js_name = "beginExecution")] diff --git a/crates/observation-tools-client/src/error.rs b/crates/observation-tools-client/src/error.rs index 248c3b0..2196ada 100644 --- a/crates/observation-tools-client/src/error.rs +++ b/crates/observation-tools-client/src/error.rs @@ -44,6 +44,9 @@ pub enum Error { /// Upload failed with error #[error("Upload failed: {0}")] UploadFailed(String), + + #[error("Creation error")] + CreationError, } impl From> for Error { diff --git a/crates/observation-tools-client/src/execution.rs b/crates/observation-tools-client/src/execution.rs index 7bcc91d..a098dfda 100644 --- a/crates/observation-tools-client/src/execution.rs +++ b/crates/observation-tools-client/src/execution.rs @@ -211,4 +211,99 @@ impl ExecutionHandle { Ok(observation_id.to_string()) } + + /// Create and send an observation with a specific ID (for testing) + /// + /// This allows tests to create an observation with a known ID, enabling + /// navigation to the observation URL before the observation is uploaded. + /// + /// # Arguments + /// * `id` - The observation ID to use + /// * `name` - The name of the observation + /// * `payload_json` - The data to observe as a JSON string + /// * `labels` - Optional array of labels for categorization + /// * `source_file` - Optional source file path + /// * `source_line` - Optional source line number + /// * `metadata` - Optional metadata as an array of [key, value] pairs + #[napi(js_name = "observeWithId", ts_return_type = "string")] + pub fn observe_with_id( + &self, + id: String, + name: String, + payload_json: String, + labels: Option>, + source_file: Option, + source_line: Option, + metadata: Option>>, + ) -> napi::Result { + use observation_tools_shared::models::Observation; + use observation_tools_shared::models::Payload; + use observation_tools_shared::models::SourceInfo; + use std::collections::HashMap; + + let observation_id = ObservationId::parse(&id) + .map_err(|e| napi::Error::from_reason(format!("Invalid observation ID: {}", e)))?; + + // Validate that it's valid JSON + serde_json::from_str::(&payload_json) + .map_err(|e| napi::Error::from_reason(format!("Invalid JSON payload: {}", e)))?; + + let size = payload_json.len(); + let payload_data = Payload { + mime_type: "application/json".to_string(), + data: payload_json, + size, + }; + + let source = match (source_file, source_line) { + (Some(file), Some(line)) => Some(SourceInfo { + file, + line, + column: None, + }), + _ => None, + }; + + // Convert metadata from array of [key, value] pairs to HashMap + let metadata_map = metadata + .unwrap_or_default() + .into_iter() + .filter_map(|pair| { + if pair.len() == 2 { + Some((pair[0].clone(), pair[1].clone())) + } else { + None + } + }) + .collect::>(); + + let observation = Observation { + id: observation_id, + execution_id: self.execution_id, + name: name.clone(), + observation_type: observation_tools_shared::ObservationType::Payload, + log_level: observation_tools_shared::LogLevel::Info, + labels: labels.unwrap_or_default(), + metadata: metadata_map, + source, + parent_span_id: None, + payload: payload_data, + created_at: chrono::Utc::now(), + }; + + self + .send_observation(observation) + .map_err(|e| napi::Error::from_reason(format!("Failed to send observation: {}", e)))?; + + log::info!( + "Observation '{}' created with ID {}: {}/exe/{}/obs/{}", + name, + observation_id, + self.base_url, + self.execution_id, + observation_id + ); + + Ok(observation_id.to_string()) + } } diff --git a/crates/observation-tools-client/src/observation_handle.rs b/crates/observation-tools-client/src/observation_handle.rs index b38f4f2..e246f81 100644 --- a/crates/observation-tools-client/src/observation_handle.rs +++ b/crates/observation-tools-client/src/observation_handle.rs @@ -40,10 +40,10 @@ impl SendObservation { } } - pub async fn wait_for_upload(mut self) -> Result { + pub async fn wait_for_upload(&mut self) -> Result { // Return creation error if present - if let Some(err) = self.creation_error { - return Err(err); + if let Some(_err) = &self.creation_error { + return Err(Error::CreationError); } // Get receiver (must be mutable for borrow_and_update and changed) diff --git a/crates/observation-tools-client/test-results/.last-run.json b/crates/observation-tools-client/test-results/.last-run.json new file mode 100644 index 0000000..544c11f --- /dev/null +++ b/crates/observation-tools-client/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "failed", + "failedTests": [] +} diff --git a/crates/observation-tools-server/Cargo.toml b/crates/observation-tools-server/Cargo.toml index d3aa1c5..c413a2a 100644 --- a/crates/observation-tools-server/Cargo.toml +++ b/crates/observation-tools-server/Cargo.toml @@ -10,6 +10,7 @@ name = "observation-tools" path = "src/main.rs" [dependencies] +url.workspace = true anyhow.workspace = true async-trait.workspace = true axum.workspace = true diff --git a/crates/observation-tools-server/src/ui/mod.rs b/crates/observation-tools-server/src/ui/mod.rs index 5f78c69..76f0f30 100644 --- a/crates/observation-tools-server/src/ui/mod.rs +++ b/crates/observation-tools-server/src/ui/mod.rs @@ -1,6 +1,6 @@ //! Web UI handlers -mod execution_detail; +pub mod execution_detail; mod executions_list; mod index; mod observation_detail; diff --git a/crates/observation-tools-server/src/ui/observation_detail.rs b/crates/observation-tools-server/src/ui/observation_detail.rs index 6f90b23..cfede5b 100644 --- a/crates/observation-tools-server/src/ui/observation_detail.rs +++ b/crates/observation-tools-server/src/ui/observation_detail.rs @@ -5,20 +5,17 @@ use crate::csrf::CsrfToken; use crate::storage::MetadataStorage; use axum::extract::Path; use axum::extract::State; -use axum::http::HeaderMap; use axum::response::Html; use minijinja::context; use minijinja_autoreload::AutoReloader; -use observation_tools_shared::models::ExecutionId; use std::sync::Arc; /// Observation detail (for the side panel) -#[tracing::instrument(skip(metadata, templates, headers))] +#[tracing::instrument(skip(metadata, templates))] pub async fn observation_detail( State(metadata): State>, State(templates): State>, Path((execution_id, observation_id)): Path<(String, String)>, - headers: HeaderMap, csrf: CsrfToken, ) -> Result, AppError> { tracing::debug!( @@ -26,41 +23,20 @@ pub async fn observation_detail( observation_id = %observation_id, "Rendering observation detail page" ); - - let _execution_id = ExecutionId::parse(&execution_id)?; - let observation_id = observation_tools_shared::ObservationId::parse(&observation_id)?; - - let observations = metadata.get_observations(&[observation_id]).await?; - - let observation = observations.into_iter().next().ok_or_else(|| { - crate::storage::StorageError::NotFound(format!("Observation {} not found", observation_id)) - })?; - - tracing::debug!(observation_name = %observation.name, "Retrieved observation for UI"); - - let env = templates.acquire_env()?; - - // Check if this is an HTMX request (for side panel) - let is_htmx_request = headers - .get("hx-request") - .and_then(|v| v.to_str().ok()) - .map(|v| v == "true") - .unwrap_or(false); - - // Use partial template for HTMX requests, full template otherwise - let template_name = if is_htmx_request { - "observation_detail_partial.html" - } else { - "observation_detail.html" + let parsed_observation_id = observation_tools_shared::ObservationId::parse(&observation_id)?; + let observation = match metadata.get_observations(&[parsed_observation_id]).await { + Ok(observations) => observations.into_iter().next(), + Err(crate::storage::StorageError::NotFound(_)) => None, + Err(e) => return Err(e.into()), }; - - let tmpl = env.get_template(template_name)?; - + let env = templates.acquire_env()?; + let tmpl = env.get_template("observation_detail.html")?; let html = tmpl.render(context! { observation => observation, + execution_id => execution_id, + observation_id => observation_id, display_threshold => observation_tools_shared::DISPLAY_THRESHOLD_BYTES, csrf_token => csrf.0, })?; - Ok(Html(html)) } diff --git a/crates/observation-tools-server/src/ui/templates.rs b/crates/observation-tools-server/src/ui/templates.rs index cfa6b1d..4d332e9 100644 --- a/crates/observation-tools-server/src/ui/templates.rs +++ b/crates/observation-tools-server/src/ui/templates.rs @@ -45,6 +45,24 @@ pub fn render_markdown(value: String) -> String { ammonia::clean(&html_output) } +/// Parse a JSON string and return a result object for template rendering. +/// Returns { ok: true, value: } on success, { ok: false } on failure. +pub fn parse_json(value: String) -> Value { + match serde_json::from_str::(&value) { + Ok(json) => { + let mut map = std::collections::BTreeMap::new(); + map.insert("ok".to_string(), Value::from(true)); + map.insert("value".to_string(), Value::from_serialize(&json)); + Value::from_iter(map) + } + Err(_) => { + let mut map = std::collections::BTreeMap::new(); + map.insert("ok".to_string(), Value::from(false)); + Value::from_iter(map) + } + } +} + /// Initialize the template auto-reloader pub fn init_templates() -> Arc { Arc::new(AutoReloader::new(move |notifier| { @@ -65,6 +83,9 @@ pub fn init_templates() -> Arc { // Add render_markdown filter to convert markdown to sanitized HTML env.add_filter("render_markdown", render_markdown); + // Add parse_json filter to parse JSON strings for template rendering + env.add_filter("parse_json", parse_json); + if cfg!(debug_assertions) { tracing::info!("Running in local development mode, enabling autoreload for templates"); let template_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates"); diff --git a/crates/observation-tools-server/static/styles.css b/crates/observation-tools-server/static/styles.css index e938dd0..37ca18c 100644 --- a/crates/observation-tools-server/static/styles.css +++ b/crates/observation-tools-server/static/styles.css @@ -1274,6 +1274,143 @@ min-height: 52px; } +/* JSON Viewer Styles */ + +.json-body { + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 14px; + line-height: 1.6; + word-wrap: break-word; + overflow-wrap: break-word; + /* Light mode colors */ + --json-key-color: #0550ae; + --json-string-color: #0a3069; + --json-number-color: #0550ae; + --json-bool-color: #cf222e; + --json-null-color: #6e7781; + --json-bracket-color: #1f2328; + --json-preview-color: #656d76; +} + +/* Dark mode colors */ + +@media (prefers-color-scheme: dark) { + .json-body { + --json-key-color: #79c0ff; + --json-string-color: #a5d6ff; + --json-number-color: #79c0ff; + --json-bool-color: #ff7b72; + --json-null-color: #8b949e; + --json-bracket-color: #f0f6fc; + --json-preview-color: #8b949e; + } +} + +.json-body .json-key { + color: var(--json-key-color); +} + +.json-body .json-string { + color: var(--json-string-color); + word-break: break-word; + white-space: pre-wrap; +} + +.json-body .json-number { + color: var(--json-number-color); +} + +.json-body .json-bool { + color: var(--json-bool-color); +} + +.json-body .json-null { + color: var(--json-null-color); + font-style: italic; +} + +.json-body .json-bracket { + color: var(--json-bracket-color); + font-weight: 500; +} + +.json-body .json-preview { + color: var(--json-preview-color); + font-size: 0.85em; +} + +/* Details/Summary styling for expand/collapse */ + +.json-body details { + display: inline; +} + +.json-body details > summary { + cursor: pointer; + list-style: none; + display: inline; +} + +.json-body details > summary::-webkit-details-marker { + display: none; +} + +.json-body details > summary::marker { + display: none; + content: ""; +} + +.json-body details > summary .json-preview::before { + content: "▶ "; + font-size: 0.7em; + margin-right: 0.1em; +} + +.json-body details[open] > summary .json-preview::before { + content: "▼ "; +} + +/* Hide preview text when expanded */ + +.json-body details[open] > summary .json-preview { + display: none; +} + +/* Content indentation */ + +.json-body .json-content { + display: block; + margin-left: 1.5rem; + border-left: 1px solid var(--json-bracket-color); + padding-left: 0.75rem; + border-left-color: color-mix(in srgb, var(--json-bracket-color) 30%, transparent); +} + +.json-body .json-item { + display: block; +} + +/* Closing bracket positioning */ + +.json-body details > .json-bracket:last-child { + display: block; +} + +.json-body details:not([open]) > .json-bracket:last-child { + display: none; +} + +.json-body details:not([open]) > .json-content { + display: none; +} + *, ::before, ::after { @@ -1864,20 +2001,46 @@ body { } } -.fixed { - position: fixed; +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } } -.bottom-0 { - bottom: 0px; +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } } -.right-0 { - right: 0px; +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } } -.top-0 { - top: 0px; +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.col-span-2 { + grid-column: span 2 / span 2; +} + +.m-0 { + margin: 0px; } .my-2 { @@ -1898,10 +2061,6 @@ body { margin-bottom: 1rem; } -.mr-\[50\%\] { - margin-right: 50%; -} - .mt-4 { margin-top: 1rem; } @@ -1926,10 +2085,6 @@ body { display: grid; } -.hidden { - display: none; -} - .h-6 { height: 1.5rem; } @@ -1938,6 +2093,10 @@ body { height: 100%; } +.h-screen { + height: 100vh; +} + .min-h-0 { min-height: 0px; } @@ -1950,10 +2109,6 @@ body { width: 1.5rem; } -.w-\[50\%\] { - width: 50%; -} - .flex-shrink-0 { flex-shrink: 0; } @@ -1966,6 +2121,10 @@ body { flex-grow: 1; } +.grow { + flex-grow: 1; +} + .cursor-not-allowed { cursor: not-allowed; } @@ -1978,8 +2137,12 @@ body { list-style-type: none; } -.grid-cols-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.grid-rows-\[auto_1fr\] { + grid-template-rows: auto 1fr; } .flex-col { @@ -2010,10 +2173,18 @@ body { gap: 2rem; } +.overflow-auto { + overflow: auto; +} + .overflow-hidden { overflow: hidden; } +.overflow-y-auto { + overflow-y: auto; +} + .truncate { overflow: hidden; text-overflow: ellipsis; @@ -2080,6 +2251,14 @@ body { background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); } +.p-4 { + padding: 1rem; +} + +.p-8 { + padding: 2rem; +} + .px-1 { padding-left: 0.25rem; padding-right: 0.25rem; diff --git a/crates/observation-tools-server/styles/input.css b/crates/observation-tools-server/styles/input.css index 82c3294..f187e0f 100644 --- a/crates/observation-tools-server/styles/input.css +++ b/crates/observation-tools-server/styles/input.css @@ -1,4 +1,5 @@ @import "github-markdown.css"; +@import "json-viewer.css"; @tailwind base; @tailwind components; diff --git a/crates/observation-tools-server/styles/json-viewer.css b/crates/observation-tools-server/styles/json-viewer.css new file mode 100644 index 0000000..01e7c7e --- /dev/null +++ b/crates/observation-tools-server/styles/json-viewer.css @@ -0,0 +1,134 @@ +/* JSON Viewer Styles */ +.json-body { + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 14px; + line-height: 1.6; + word-wrap: break-word; + overflow-wrap: break-word; + + /* Light mode colors */ + --json-key-color: #0550ae; + --json-string-color: #0a3069; + --json-number-color: #0550ae; + --json-bool-color: #cf222e; + --json-null-color: #6e7781; + --json-bracket-color: #1f2328; + --json-preview-color: #656d76; +} + +/* Dark mode colors */ +@media (prefers-color-scheme: dark) { + .json-body { + --json-key-color: #79c0ff; + --json-string-color: #a5d6ff; + --json-number-color: #79c0ff; + --json-bool-color: #ff7b72; + --json-null-color: #8b949e; + --json-bracket-color: #f0f6fc; + --json-preview-color: #8b949e; + } +} + +.json-body .json-key { + color: var(--json-key-color); +} + +.json-body .json-string { + color: var(--json-string-color); + word-break: break-word; + white-space: pre-wrap; +} + +.json-body .json-number { + color: var(--json-number-color); +} + +.json-body .json-bool { + color: var(--json-bool-color); +} + +.json-body .json-null { + color: var(--json-null-color); + font-style: italic; +} + +.json-body .json-bracket { + color: var(--json-bracket-color); + font-weight: 500; +} + +.json-body .json-preview { + color: var(--json-preview-color); + font-size: 0.85em; +} + +/* Details/Summary styling for expand/collapse */ +.json-body details { + display: inline; +} + +.json-body details > summary { + cursor: pointer; + list-style: none; + display: inline; +} + +.json-body details > summary::-webkit-details-marker { + display: none; +} + +.json-body details > summary::marker { + display: none; + content: ""; +} + +.json-body details > summary .json-preview::before { + content: "▶ "; + font-size: 0.7em; + margin-right: 0.1em; +} + +.json-body details[open] > summary .json-preview::before { + content: "▼ "; +} + +/* Hide preview text when expanded */ +.json-body details[open] > summary .json-preview { + display: none; +} + +/* Content indentation */ +.json-body .json-content { + display: block; + margin-left: 1.5rem; + border-left: 1px solid var(--json-bracket-color); + padding-left: 0.75rem; +} + +.json-body .json-content { + border-left-color: color-mix(in srgb, var(--json-bracket-color) 30%, transparent); +} + +.json-body .json-item { + display: block; +} + +/* Closing bracket positioning */ +.json-body details > .json-bracket:last-child { + display: block; +} + +.json-body details:not([open]) > .json-bracket:last-child { + display: none; +} + +.json-body details:not([open]) > .json-content { + display: none; +} diff --git a/crates/observation-tools-server/templates/_json_value.html b/crates/observation-tools-server/templates/_json_value.html new file mode 100644 index 0000000..c30549e --- /dev/null +++ b/crates/observation-tools-server/templates/_json_value.html @@ -0,0 +1,73 @@ +{# Recursive macro to render JSON values with syntax highlighting and collapsible objects/arrays. +Usage: {{ render_json(value, is_last) }} - value: The JSON value to render (can be object, array, +string, number, boolean, or null) - is_last: Whether this is the last item in a parent container +(controls trailing comma) #} +{% macro render_json(value, is_last=true) %} + {#- Check for +null/undefined FIRST since none passes other type checks -#} + {%- + if value is undefined or value is + none + -%} + null{% if not is_last %},{% endif %} + {%- elif value == true -%} + true{% if not is_last %},{% endif %} + {%- elif value == false -%} + false{% if not is_last %},{% endif %} + {%- elif value is string -%} + "{{ value | e }}"{% if not is_last %},{% endif %} + {%- + elif value is + number + -%} + {{ value }}{% if not is_last %},{% endif %} + {%- + elif + value is mapping + -%} + {# Object #} + {%- set items = value | items -%} + {%- if items | length == 0 -%} + {}{% if not is_last %},{% endif %} + {%- else -%} +
+ + { + {{ items | length }} {% if items | length == 1 %}key{% else %}keys{% endif %} + +
+ {%- for item in items -%} +
+ "{{ item[0] }}": {{ render_json(item[1], loop.last) }} +
+ {%- endfor -%} +
+ }{% if not is_last %},{% endif %} +
+ {%- endif -%} + {%- elif value is iterable -%} + {# Array #} + {%- if value | length == 0 -%} + []{% if not is_last %},{% endif %} + {%- else -%} +
+ + [ + {{ value | length }} {% if value | length == 1 %}item{% else %}items{% endif %} + +
+ {%- for item in value -%} +
{{ render_json(item, loop.last) }}
+ {%- endfor -%} +
+ ]{% if not is_last %},{% endif %} +
+ {%- endif -%} + {%- else -%} + "{{ value | e }}"{% if not is_last %},{% endif %} + {%- endif -%} +{% endmacro %} diff --git a/crates/observation-tools-server/templates/_nav_bar.html b/crates/observation-tools-server/templates/_nav_bar.html new file mode 100644 index 0000000..5ab313a --- /dev/null +++ b/crates/observation-tools-server/templates/_nav_bar.html @@ -0,0 +1,25 @@ +{% macro nav(class="") %} + +{% endmacro %} diff --git a/crates/observation-tools-server/templates/_observation_content.html b/crates/observation-tools-server/templates/_observation_content.html index 092fae2..09b7008 100644 --- a/crates/observation-tools-server/templates/_observation_content.html +++ b/crates/observation-tools-server/templates/_observation_content.html @@ -1,3 +1,4 @@ +{% from "_json_value.html" import render_json %}

{{ observation.name }}

@@ -11,54 +12,59 @@

{{ observation.name }}

>

- created: {{ - observation.created_at }} + created: + {{ observation.created_at }}

{% if observation.source %} -

- source: - {{ observation.source.file }}:{{ observation.source.line }} -

- {% endif %} {% if observation.parent_span_id %} -

- parent span: - {{ observation.parent_span_id }} -

- {% endif %} {% if observation.labels %} -

- labels: {{ - observation.labels|join(", ") }} -

- {% endif %} {% if observation.metadata %} -

metadata

-
    - {% for item in observation.metadata | items %} -
  • - {{ item[0] }}: - {{ item[1] }} -
  • - {% endfor %} -
+

+ source: + {{ observation.source.file }}:{{ observation.source.line }} +

+ {% endif %} + {% if observation.parent_span_id %} +

+ parent span: + {{ observation.parent_span_id }} +

+ {% endif %} + {% if observation.labels %} +

+ labels: + {{ observation.labels|join(", ") }} +

+ {% endif %} + {% if observation.metadata %} +

metadata

+
    + {% for item in observation.metadata | items %} +
  • + {{ item[0] }}: + {{ item[1] }} +
  • + {% endfor %} +
{% endif %}

payload

- type: {{ - observation.payload.mime_type }} + type: + {{ observation.payload.mime_type }}

- size: {{ - observation.payload.size }} bytes + size: + {{ observation.payload.size }} bytes

payload

{% if observation.payload.size > display_threshold %} -
-

Payload is too large to display inline.

-
+
+

Payload is too large to display inline.

+
{% elif observation.payload.mime_type == "text/markdown" %} -
- {{ observation.payload.data|render_markdown|safe }} -
+
+ {{ observation.payload.data|render_markdown|safe }} +
+ {% elif observation.payload.mime_type == "application/json" %} + {% set json_result = observation.payload.data | parse_json %} + {% if json_result.ok %} +
+ {{ render_json(json_result.value) }} +
+ {% else %} +
+        {{ observation.payload.data|unescape }}
+      
+ {% endif %} {% else %} -
-{{ observation.payload.data|unescape }}
+
+      {{ observation.payload.data|unescape }}
+    
{% endif %}
diff --git a/crates/observation-tools-server/templates/base.html b/crates/observation-tools-server/templates/base.html index 78fdcca..aa97345 100644 --- a/crates/observation-tools-server/templates/base.html +++ b/crates/observation-tools-server/templates/base.html @@ -18,13 +18,16 @@ .logo { display: block; } + .logo-dark { display: none; } + @media (prefers-color-scheme: dark) { .logo { display: none; } + .logo-dark { display: block; } @@ -32,30 +35,6 @@ - - -
{% block content %}{% endblock %}
+ {% block content %}{% endblock %} diff --git a/crates/observation-tools-server/templates/execution_detail.html b/crates/observation-tools-server/templates/execution_detail.html index 0580e70..cfe63ec 100644 --- a/crates/observation-tools-server/templates/execution_detail.html +++ b/crates/observation-tools-server/templates/execution_detail.html @@ -1,223 +1,263 @@ -{% extends "base.html" %} {% block title %}{% if execution %}{{ execution.name }}{% else %}Waiting -for execution...{% endif %}{% endblock %} {% block content %} - -
- {% if execution %} -
-

- home - / - executions + {{ nav() }} +

+
- / {{ execution.name }} -

+ {% if execution %} +
+

+ home + / + executions + / {{ execution.name }} +

-

{{ execution.name }}

+

+ {{ execution.name }} +

-

- id: - {{ execution.id }} -

-

- created: {{ - execution.created_at }} -

+

+ id: + {{ execution.id }} +

+

+ created: + {{ execution.created_at }} +

- {% if execution.metadata %} -

metadata

-
    - {% for item in execution.metadata | items %} -
  • {{ item[0] }}: {{ item[1] }}
  • - {% endfor %} -
- {% endif %} + {% if execution.metadata %} +

metadata

+
    + {% for item in execution.metadata | items %} +
  • {{ item[0] }}: {{ item[1] }}
  • + {% endfor %} +
+ {% endif %} -
+
-
- log - payload -
+
+ log + payload +
-

observations

+

observations

-
- {% if observations %} {% if view == 'log' %} -
- {% for obs in observations %} - + {% if observations %} + {% if view == 'log' %} + + {% else %} +
    + {% for obs in observations %} +
  • + {{ obs.name }} + + — {{ obs.payload.mime_type }} + {% if obs.source %}— {{ obs.source.file }}:{{ obs.source.line }}{% endif %} + — {{ obs.created_at }} + + {% if obs.labels %} +
    labels: {{ obs.labels|join(", ") }} + {% endif %} +
  • + {% endfor %} +
+ {% endif %} + {% if total_count %} +
+

+ + showing {{ offset + 1 }}-{{ offset + observations|length }} of + {{ total_count }} (page {{ page }}) + +

+
+ {% if offset > 0 %} + + {% else %} + + {% endif %} + {% if has_next_page %} + + {% else %} + + {% endif %} +
+
+ {% endif %} + {% else %} +

no observations found.

+ {% endif %} +
+
+ {% else %} +
+

+ home + / + executions + / waiting... +

+ +

Waiting for execution...

+ +

+ id: + {{ execution_id }} +

+

+ This execution has not been received yet. The page will automatically refresh when + data arrives. +

+
+ {% endif %} +
+ + {% if selected_observation %} + -
- {{ obs.created_at }} - {{ obs.log_level }} - {% if obs.source %}{{ obs.source.file }}:{{ obs.source.line }}{% else %}-{% endif - %} - {% if obs.observation_type == 'LogEntry' %}{{ obs.payload.data[:200] }}{% if - obs.payload.data|length > 200 %}...{% endif %}{% else %}{{ obs.name }}: {{ - obs.payload.data[:100] }}{% if obs.payload.data|length > 100 %}...{% endif %}{% endif - %} + ×
- - {% endfor %} -
- {% else %} -
    - {% for obs in observations %} -
  • - {{ obs.name }} - - — {{ obs.payload.mime_type }} {% if obs.source %}— {{ obs.source.file }}:{{ - obs.source.line }}{% endif %} — {{ obs.created_at }} - - {% if obs.labels %} -
    labels: {{ obs.labels|join(", ") }} - {% endif %} -
  • - {% endfor %} -
- {% endif %} {% if total_count %} -
-

- - showing {{ offset + 1 }}-{{ offset + observations|length }} of {{ total_count }} (page - {{ page }}) - -

-
- {% if offset > 0 %} - - {% else %} - - {% endif %} {% if has_next_page %} - - {% else %} - - {% endif %} -
-
- {% endif %} {% else %} -

no observations found.

+
+ {% set observation = selected_observation %} {% include "_observation_content.html" %} +
+ {% endif %}
- - {% if selected_observation %} - - {% endif %} {% else %} -
-

- home - / - executions - / waiting... -

- -

Waiting for execution...

- -

- id: - {{ execution_id }} -

-

- This execution has not been received yet. The page will automatically refresh when data - arrives. -

-
- {% endif %} - {% endblock %} diff --git a/crates/observation-tools-server/templates/executions_list.html b/crates/observation-tools-server/templates/executions_list.html index c38ec58..34b4a36 100644 --- a/crates/observation-tools-server/templates/executions_list.html +++ b/crates/observation-tools-server/templates/executions_list.html @@ -1,76 +1,84 @@ -{% extends "base.html" %} {% block title %}Executions{% endblock %} {% block content %} -

executions

+{% from "_nav_bar.html" import nav %} +{% extends "base.html" %} +{% block title %}Executions{% endblock %} +{% block content %} + {{ nav() }} +
+

executions

-
- {% if executions %} -
    - {% for execution in executions %} -
  • - {{ execution.name }} - — {{ execution.created_at }} -
  • - {% endfor %} -
+
+ {% if executions %} +
    + {% for execution in executions %} +
  • + {{ execution.name }} + — {{ execution.created_at }} +
  • + {% endfor %} +
- {% if total_count %} -
-

- - showing {{ offset + 1 }}-{{ offset + executions|length }} of {{ total_count }} (page {{ page - }}) - -

-
- {% if offset > 0 %} - + {% if total_count %} +
+

+ + showing {{ offset + 1 }}-{{ offset + executions|length }} of {{ total_count }} (page + {{ page }}) + +

+
+ {% if offset > 0 %} + + {% else %} + + {% endif %} + {% if has_next_page %} + + {% else %} + + {% endif %} +
+
+ {% endif %} {% else %} - - {% endif %} {% if has_next_page %} - - {% else %} - +

no executions found.

{% endif %}
-
- {% endif %} {% else %} -

no executions found.

- {% endif %} -
+
{% endblock %} diff --git a/crates/observation-tools-server/templates/index.html b/crates/observation-tools-server/templates/index.html index 6ba7252..b8cb364 100644 --- a/crates/observation-tools-server/templates/index.html +++ b/crates/observation-tools-server/templates/index.html @@ -1,30 +1,36 @@ -{% extends "base.html" %} {% block title %}Observation Tools{% endblock %} {% block content %} -

observation tools

+{% from "_nav_bar.html" import nav %} +{% extends "base.html" %} +{% block title %}Observation Tools{% endblock %} +{% block content %} + {{ nav() }} +
+

observation tools

-

- a developer data inspection toolkit for exporting, visualizing, and inspecting data from anywhere - in a program. -

+

+ a developer data inspection toolkit for exporting, visualizing, and inspecting data from + anywhere in a program. +

-

how it works

+

how it works

-

- 1. instrument: add observations to your code
- 2. collect: server stores all observations
- 3. inspect: view and search observations here -

+

+ 1. instrument: add observations to your code
+ 2. collect: server stores all observations
+ 3. inspect: view and search observations here +

-
+
-

getting started

+

getting started

-

- 1. install client library
- 2. start server: - cargo observation-tools serve
- 3. instrument your code
- 4. view results here -

+

+ 1. install client library
+ 2. start server: + cargo observation-tools serve
+ 3. instrument your code
+ 4. view results here +

+
{% endblock %} diff --git a/crates/observation-tools-server/templates/observation_detail.html b/crates/observation-tools-server/templates/observation_detail.html index 40965c7..18f6d48 100644 --- a/crates/observation-tools-server/templates/observation_detail.html +++ b/crates/observation-tools-server/templates/observation_detail.html @@ -1,23 +1,76 @@ -{% extends "base.html" %} {% block title %}{{ observation.name }}{% endblock %} {% block content %} -

- home - / - executions - / - {{ observation.execution_id }} - / {{ observation.name }} -

+{% from "_nav_bar.html" import nav %} +{% extends "base.html" %} +{% block title %}{% if observation %}{{ observation.name }}{% else %}Waiting for observation...{% endif %}{% endblock %} +{% block content %} + {{ nav() }} +
+
+ {% if observation %} +

+ home + / + executions + / + {{ observation.execution_id }} + / {{ observation.name }} +

-{% include "_observation_content.html" %} {% endblock %} + {% include "_observation_content.html" %} + {% else %} +
+

+ home + / + executions + / + {{ execution_id }} + / waiting... +

+ +

Waiting for observation...

+ +

+ id: + {{ observation_id }} +

+

+ This observation has not been received yet. The page will automatically refresh when + data arrives. +

+
+ {% endif %} +
+
+{% endblock %} diff --git a/crates/observation-tools-server/templates/observation_detail_partial.html b/crates/observation-tools-server/templates/observation_detail_partial.html deleted file mode 100644 index b3d4cbd..0000000 --- a/crates/observation-tools-server/templates/observation_detail_partial.html +++ /dev/null @@ -1 +0,0 @@ -{% include "_observation_content.html" %} diff --git a/package-lock.json b/package-lock.json index 93ae7a8..fc7f91e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -604,6 +604,7 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -847,6 +848,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", diff --git a/package.json b/package.json index 079f711..2461ec4 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,12 @@ "private": true, "scripts": { "build:css": "tailwindcss -i ./crates/observation-tools-server/styles/input.css -o ./crates/observation-tools-server/static/styles.css", - "watch:css": "tailwindcss -i ./crates/observation-tools-server/styles/input.css -o ./crates/observation-tools-server/static/styles.css --watch" + "watch:css": "tailwindcss -i ./crates/observation-tools-server/styles/input.css -o ./crates/observation-tools-server/static/styles.css --watch", + "format": "cargo +nightly fmt && prettier --write ." }, "devDependencies": { + "prettier": "^3.7.4", + "prettier-plugin-jinja-template": "^2.1.0", "tailwindcss": "^3.4.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb6b3bf..5614673 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,12 @@ settings: importers: .: devDependencies: + prettier: + specifier: ^3.7.4 + version: 3.7.4 + prettier-plugin-jinja-template: + specifier: ^2.1.0 + version: 2.1.0(prettier@3.7.4) tailwindcss: specifier: ^3.4.1 version: 3.4.18 @@ -418,6 +424,22 @@ packages: } engines: { node: ^10 || ^12 || >=14 } + prettier-plugin-jinja-template@2.1.0: + resolution: + { + integrity: sha512-mzoCp2Oy9BDSug80fw3B3J4n4KQj1hRvoQOL1akqcDKBb5nvYxrik9zUEDs4AEJ6nK7QDTGoH0y9rx7AlnQ78Q==, + } + peerDependencies: + prettier: ^3.0.0 + + prettier@3.7.4: + resolution: + { + integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==, + } + engines: { node: ">=14" } + hasBin: true + queue-microtask@1.2.3: resolution: { @@ -722,6 +744,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prettier-plugin-jinja-template@2.1.0(prettier@3.7.4): + dependencies: + prettier: 3.7.4 + + prettier@3.7.4: {} + queue-microtask@1.2.3: {} read-cache@1.0.0: diff --git a/tests/helpers/testIds.ts b/tests/helpers/testIds.ts index caad045..2cc40e7 100644 --- a/tests/helpers/testIds.ts +++ b/tests/helpers/testIds.ts @@ -32,6 +32,10 @@ export enum TestId { ObservationMetadataKey = "ObservationMetadataKey", ObservationMetadataValue = "ObservationMetadataValue", + // JSON Viewer + JsonCollapsibleArea = "JsonCollapsibleArea", + JsonCollapseToggle = "JsonCollapseToggle", + // Pagination PaginationNext = "PaginationNext", PaginationPrev = "PaginationPrev", diff --git a/tests/playwright.config.ts b/tests/playwright.config.ts index 51410e7..c8e4eeb 100644 --- a/tests/playwright.config.ts +++ b/tests/playwright.config.ts @@ -1,10 +1,21 @@ import { defineConfig } from "@playwright/test"; +const isCI = !!process.env.CI; + export default defineConfig({ - fullyParallel: !process.env.SERVER_URL, testDir: "./specs", + fullyParallel: !process.env.SERVER_URL, + forbidOnly: isCI, + retries: isCI ? 2 : 0, + workers: isCI ? 1 : undefined, + timeout: 60000, + reporter: isCI + ? [["github"], ["html", { outputFolder: "playwright-report", open: "never" }]] + : [["list"], ["html", { outputFolder: "playwright-report", open: "on-failure" }]], + outputDir: "test-results", use: { + trace: "on-first-retry", + screenshot: "only-on-failure", video: "retain-on-failure", }, - timeout: 60000, }); diff --git a/tests/specs/json.spec.ts b/tests/specs/json.spec.ts new file mode 100644 index 0000000..1046baa --- /dev/null +++ b/tests/specs/json.spec.ts @@ -0,0 +1,62 @@ +import { expect, test } from "../fixtures"; +import { ObservationBuilder } from "observation-tools-client"; +import { TestId } from "../helpers/testIds"; + +test("JSON observation is rendered with syntax highlighting", async ({ page, server }) => { + const client = server.createClient(); + const executionName = "execution-with-json"; + const exe = client.beginExecution(executionName); + + const observationName = "json-observation"; + const jsonContent = JSON.stringify({ + name: "test", + count: 42, + active: true, + items: ["a", "b", "c"], + nested: { + key: "value", + }, + emptyArray: [], + emptyObject: {}, + longStringShouldWrap: "a".repeat(500), + nullValue: null, + malicious: '', + imgXss: '', + }); + + const handle = new ObservationBuilder(observationName).jsonPayload(jsonContent).send(exe); + + await page.goto(handle.handle().url); + const payloadElement = page.getByTestId(TestId.ObservationPayload); + await expect(payloadElement).toBeVisible(); + await expect(payloadElement).toHaveScreenshot(); + await expect(payloadElement.locator("script")).toHaveCount(0); + await expect(payloadElement).toContainText("