diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 9f6b568..5e4377a 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -15,8 +15,10 @@ jobs: with: cache: true experimental: true - log_level: ${{ runner.debug == '1' && 'debug' || 'info' }} + log_level: ${{ runner.debug == '1' && 'trace' || 'info' }} - run: mise build && mise check + env: + MISE_LOG_LEVEL: ${{ runner.debug == '1' && 'trace' || 'info' }} test: strategy: @@ -30,5 +32,9 @@ jobs: with: cache: true experimental: true - log_level: ${{ runner.debug == '1' && 'debug' || 'info' }} + log_level: ${{ runner.debug == '1' && 'trace' || 'info' }} - run: mise test + env: + MISE_LOG_LEVEL: ${{ runner.debug == '1' && 'trace' || 'info' }} + LOGTAPE_TEST_MODE: ${{ runner.debug == '1' && 'always' || 'on-failure' }} + LOGTAPE_TEST_LOWEST_LEVEL: ${{ runner.debug == '1' && 'trace' || 'debug' }} diff --git a/.oxlintrc.json b/.oxlintrc.json index 3e6e0a8..082f348 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -39,6 +39,7 @@ "import/consistent-type-specifier-style": "off", "import/exports-last": "off", "import/group-exports": "off", + "import/max-dependencies": ["warn", { "max": 20 }], "import/no-default-export": "off", "import/no-named-export": "off", "import/no-namespace": "off", diff --git a/mise.toml b/mise.toml index 6c218de..eab0502 100644 --- a/mise.toml +++ b/mise.toml @@ -65,7 +65,20 @@ run = "pnpm run --parallel --recursive build" [tasks.test] description = "Run tests" depends = ["build"] -run = "pnpm run --parallel --recursive test" +usage = ''' +flag "-d --debug" help="Run tests in debug mode" +''' +run = ''' +#!/usr/bin/env nu +if "usage_debug" in $env { + $env.LOGTAPE_TEST_MODE = "always" + $env.LOGTAPE_TEST_LOWEST_LEVEL = "trace" +} else { + $env.LOGTAPE_TEST_MODE = "on-failure" + $env.LOGTAPE_TEST_LOWEST_LEVEL = "debug" +} +pnpm run --parallel --recursive test +''' [tasks.bump] description = "Bump all package versions" @@ -93,4 +106,12 @@ pnpm exec drizzle-kit generate ...$name ...$custom [tasks.dev] description = "Run the development server" +usage = ''' +flag "-L --log-level " { + choices "trace" "debug" "info" "warn" "error" "fatal" + default "debug" + help "Set log level" +} +flag "--log-output " help="Write logs to a file" +''' run = "node scripts/dev.mts" diff --git a/packages/drfed/package.json b/packages/drfed/package.json index a67061f..31ff52a 100644 --- a/packages/drfed/package.json +++ b/packages/drfed/package.json @@ -59,6 +59,7 @@ "build": "tsdown" }, "devDependencies": { + "@logtape/testing-node": "catalog:", "@types/node": "catalog:", "@types/pg": "catalog:", "tsdown": "catalog:", @@ -68,7 +69,10 @@ "@drfed/graphql": "workspace:*", "@drfed/models": "workspace:*", "@electric-sql/pglite": "catalog:", + "@logtape/drizzle-orm": "catalog:", + "@logtape/logtape": "catalog:", "@optique/core": "^1.1.0", + "@optique/logtape": "catalog:", "@optique/run": "^1.1.0", "drizzle-orm": "catalog:", "pg": "catalog:", diff --git a/packages/drfed/src/index.ts b/packages/drfed/src/index.ts index d59d78d..c40bc5d 100644 --- a/packages/drfed/src/index.ts +++ b/packages/drfed/src/index.ts @@ -13,10 +13,13 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import { AsyncLocalStorage } from "node:async_hooks"; import process from "node:process"; import { createYogaServer } from "@drfed/graphql"; import { migrate } from "@drfed/models"; +import { configure, getConsoleSink } from "@logtape/logtape"; +import { createLoggingConfig } from "@optique/logtape"; import { run } from "@optique/run"; import { serve } from "srvx"; @@ -25,6 +28,7 @@ import metadata from "../package.json" with { type: "json" }; import type { Options } from "./parser.ts"; import program from "./program.ts"; +// oxlint-disable-next-line max-lines-per-function export async function main(): Promise { const options: Options = run(program, { help: "option", @@ -35,6 +39,33 @@ export async function main(): Promise { value: metadata.version, }, }); + const loggingConfig = await createLoggingConfig( + options.logging, + {}, + { + contextLocalStorage: new AsyncLocalStorage(), + sinks: { + stderr: getConsoleSink({ + levelMap: { + trace: "error", + debug: "error", + info: "error", + warning: "error", + error: "error", + fatal: "error", + }, + }), + }, + loggers: [ + { + category: ["logtape", "meta"], + lowestLevel: "warning", + sinks: ["stderr"], + }, + ], + }, + ); + await configure(loggingConfig); if (options.drizzle.migrate) { await migrate({ credentials: options.drizzle.credentials }); } diff --git a/packages/drfed/src/parser.ts b/packages/drfed/src/parser.ts index 8c33f46..0a28bfc 100644 --- a/packages/drfed/src/parser.ts +++ b/packages/drfed/src/parser.ts @@ -14,12 +14,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { relations, schema } from "@drfed/models"; +import { getLogger } from "@logtape/drizzle-orm"; import { merge, object, or } from "@optique/core/constructs"; import { message, optionNames } from "@optique/core/message"; import { map, withDefault } from "@optique/core/modifiers"; import type { InferValue } from "@optique/core/parser"; import { option } from "@optique/core/primitives"; import { socketAddress, url } from "@optique/core/valueparser"; +import { loggingOptions } from "@optique/logtape"; import { path } from "@optique/run/valueparser"; import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; import { drizzle as drizzlePglite } from "drizzle-orm/pglite"; @@ -43,6 +45,7 @@ const pgliteParser = map( connection: { dataDir: dbPath }, relations, schema, + logger: getLogger(), }), }), ); @@ -67,11 +70,13 @@ const postgresParser = map( }, relations, schema, + logger: getLogger(), }), }), ); export const parser = object({ + logging: loggingOptions({ level: "option", short: "-L" }), address: withDefault( option("--listen", "-l", socketAddress({ requirePort: true }), { description: message`The address to listen on.`, diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 8d06686..0ca4f60 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -86,12 +86,15 @@ }, "devDependencies": { "@electric-sql/pglite": "catalog:", + "@logtape/testing-node": "catalog:", "@types/node": "catalog:", "tsdown": "catalog:", "typescript": "catalog:" }, "dependencies": { "@drfed/models": "workspace:*", + "@logtape/graphql-yoga": "catalog:", + "@logtape/logtape": "catalog:", "@pothos/core": "^4.13.0", "@pothos/plugin-drizzle": "^0.17.4", "@pothos/plugin-relay": "^4.7.0", diff --git a/packages/graphql/src/account.test.ts b/packages/graphql/src/account.test.ts index 2ec82a4..060dc6a 100644 --- a/packages/graphql/src/account.test.ts +++ b/packages/graphql/src/account.test.ts @@ -14,9 +14,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import assert from "node:assert/strict"; -import { describe, it } from "node:test"; import { type Database, schema } from "@drfed/models"; +import { describe, it } from "@logtape/testing-node/autoload"; import { withTestHarness } from "./harness.test.ts"; diff --git a/packages/graphql/src/harness-smoke.test.ts b/packages/graphql/src/harness-smoke.test.ts index d1e0c8b..a3e677d 100644 --- a/packages/graphql/src/harness-smoke.test.ts +++ b/packages/graphql/src/harness-smoke.test.ts @@ -14,9 +14,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import assert from "node:assert/strict"; -import { describe, it } from "node:test"; import { schema } from "@drfed/models"; +import { describe, it } from "@logtape/testing-node/autoload"; import { withTemporaryDatabase, withTestHarness } from "./harness.test.ts"; diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index f9c318e..ea95121 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import type { Database } from "@drfed/models"; +import { getYogaLogger } from "@logtape/graphql-yoga"; import { type YogaServerInstance, createYoga, @@ -39,5 +40,6 @@ export function createYogaServer( }, plugins: [useExecutionCancellation()], schema, + logging: getYogaLogger(), }); } diff --git a/packages/graphql/src/instance.test.ts b/packages/graphql/src/instance.test.ts index 5479556..af6ea55 100644 --- a/packages/graphql/src/instance.test.ts +++ b/packages/graphql/src/instance.test.ts @@ -14,9 +14,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import assert from "node:assert/strict"; -import { describe, it } from "node:test"; import { type Database, schema } from "@drfed/models"; +import { describe, it } from "@logtape/testing-node/autoload"; import { withTestHarness } from "./harness.test.ts"; diff --git a/packages/models/package.json b/packages/models/package.json index 3133911..54eeaeb 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -91,6 +91,7 @@ "test": "node --test" }, "devDependencies": { + "@logtape/testing-node": "catalog:", "@types/node": "catalog:", "@types/pg": "catalog:", "drizzle-kit": "1.0.0-beta.22", @@ -99,6 +100,7 @@ }, "dependencies": { "@electric-sql/pglite": "catalog:", + "@logtape/logtape": "catalog:", "drizzle-orm": "catalog:", "pg": "catalog:" } diff --git a/packages/models/src/email.test.ts b/packages/models/src/email.test.ts index 710d3ca..0587663 100644 --- a/packages/models/src/email.test.ts +++ b/packages/models/src/email.test.ts @@ -14,9 +14,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import assert from "node:assert/strict"; -import { describe, it } from "node:test"; import { normalizeEmail } from "@drfed/models/email"; +import { describe, it } from "@logtape/testing-node/autoload"; describe("normalizeEmail()", () => { it("with valid email", () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 296231c..d6a3bec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,21 @@ catalogs: '@electric-sql/pglite': specifier: ^0.5.3 version: 0.5.3 + '@logtape/drizzle-orm': + specifier: ^2.2.2 + version: 2.2.2 + '@logtape/graphql-yoga': + specifier: 2.3.0-dev.840 + version: 2.3.0-dev.840 + '@logtape/logtape': + specifier: 2.3.0-dev.840 + version: 2.3.0-dev.840 + '@logtape/testing-node': + specifier: 2.3.0-dev.840 + version: 2.3.0-dev.840 + '@optique/logtape': + specifier: ^1.1.1 + version: 1.1.1 '@types/node': specifier: ^26.0.0 version: 26.0.0 @@ -41,9 +56,18 @@ importers: '@electric-sql/pglite': specifier: 'catalog:' version: 0.5.3 + '@logtape/drizzle-orm': + specifier: 'catalog:' + version: 2.2.2(@logtape/logtape@2.3.0-dev.840) + '@logtape/logtape': + specifier: 'catalog:' + version: 2.3.0-dev.840 '@optique/core': specifier: ^1.1.0 version: 1.1.0 + '@optique/logtape': + specifier: 'catalog:' + version: 1.1.1(@logtape/logtape@2.3.0-dev.840) '@optique/run': specifier: ^1.1.0 version: 1.1.0 @@ -57,6 +81,9 @@ importers: specifier: ^0.11.16 version: 0.11.16 devDependencies: + '@logtape/testing-node': + specifier: 'catalog:' + version: 2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840) '@types/node': specifier: 'catalog:' version: 26.0.0 @@ -75,6 +102,12 @@ importers: '@drfed/models': specifier: workspace:* version: link:../models + '@logtape/graphql-yoga': + specifier: 'catalog:' + version: 2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840)(graphql-yoga@5.21.2(graphql@16.14.2))(graphql@16.14.2) + '@logtape/logtape': + specifier: 'catalog:' + version: 2.3.0-dev.840 '@pothos/core': specifier: ^4.13.0 version: 4.13.0(graphql@16.14.2) @@ -100,6 +133,9 @@ importers: '@electric-sql/pglite': specifier: 'catalog:' version: 0.5.3 + '@logtape/testing-node': + specifier: 'catalog:' + version: 2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840) '@types/node': specifier: 'catalog:' version: 26.0.0 @@ -115,6 +151,9 @@ importers: '@electric-sql/pglite': specifier: 'catalog:' version: 0.5.3 + '@logtape/logtape': + specifier: 'catalog:' + version: 2.3.0-dev.840 drizzle-orm: specifier: 'catalog:' version: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0) @@ -122,6 +161,9 @@ importers: specifier: 'catalog:' version: 8.21.0 devDependencies: + '@logtape/testing-node': + specifier: 'catalog:' + version: 2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840) '@types/node': specifier: 'catalog:' version: 26.0.0 @@ -411,6 +453,31 @@ packages: resolution: {integrity: sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==} engines: {node: '>=12'} + '@logtape/drizzle-orm@2.2.2': + resolution: {integrity: sha512-Z/TrI5BbOCSJPmoyogy9RERtAxs/hlyI+e/uay1PSY68/ZddPqfudbS6MGgFylAiACprJ0d0zEiGeysUQUcQ2A==} + peerDependencies: + '@logtape/logtape': ^2.2.2 + + '@logtape/graphql-yoga@2.3.0-dev.840': + resolution: {integrity: sha512-zL+RIlG5TPwC8iAWeW8Lh49rA6VUw9fYSFcXLvWIrTJ56xQPX0TzFQ6MMzAMMjw4GlSz8cs0HsHlI/rsHUT02w==} + peerDependencies: + '@logtape/logtape': ^2.3.0-dev.840+34e837bf + graphql: ^15.2.0 || ^16.0.0 + graphql-yoga: ^5.0.0 + + '@logtape/logtape@2.3.0-dev.840': + resolution: {integrity: sha512-ozdG01RAKtEbDO9iDZDQOyUX9/4p1AcuHaxJtarDK22ngFU7vSxXyPGn9bf89pX358v2QXY+3+CqXWMlT6tiaQ==} + + '@logtape/testing-node@2.3.0-dev.840': + resolution: {integrity: sha512-85UWXs1LCVKbL6nrcR7tPeh5/stQYwrBCkEOuUOJ0ufkTG7l6TP+eNWlCr0P0hrG4NzjjP9GhOpIamrLXbvMtg==} + peerDependencies: + '@logtape/logtape': ^2.3.0-dev.840+34e837bf + + '@logtape/testing@2.3.0-dev.840': + resolution: {integrity: sha512-Hw75jN/90zwf1ooawhs1pVBhz1C/zhEASqFznClmlFk30Tlog/rYwc+yra6vdsB6buHb0jHmIe1NHoX2vV+PDw==} + peerDependencies: + '@logtape/logtape': ^2.3.0-dev.840+34e837bf + '@napi-rs/wasm-runtime@1.1.5': resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} peerDependencies: @@ -421,6 +488,20 @@ packages: resolution: {integrity: sha512-eBqai76tHiFDoShlTNXN9AAPs9XznCJRrk4qmGhjZUSMmePCF9o1XU3okUHxHdDXXCHj4auKoIvCN79KNKErxA==} engines: {bun: '>=1.2.0', deno: '>=2.3.0', node: '>=20.0.0'} + '@optique/core@1.1.1': + resolution: {integrity: sha512-tbZLdH5wEnh8IRG8KTmWPZCKHLmD4bXigoPdzoSUVi+V46qZTNEaTdqtR7EuEwhbPI0KQjLSYRZCxFTdr9OZkA==} + engines: {bun: '>=1.2.0', deno: '>=2.3.0', node: '>=20.0.0'} + + '@optique/logtape@1.1.1': + resolution: {integrity: sha512-vpmMK0Y7dvrhQEXIGr+Pix+UXlJi4fp+NIX6s7xmpIjYfmms+lGw3dHPJhjJFoTthKJQCeBF88tjWAqwCJhVLg==} + engines: {bun: '>=1.2.0', deno: '>=2.3.0', node: '>=20.0.0'} + peerDependencies: + '@logtape/file': ^2.0.4 + '@logtape/logtape': ^2.0.4 + peerDependenciesMeta: + '@logtape/file': + optional: true + '@optique/run@1.1.0': resolution: {integrity: sha512-dcuqqqU1Cpm9CLGEkCkpT/cpJ6H6a+hs0rP+iD8Tgwb+CPPZtX/hCfdIrqYyZ2RtYLxgc3S6KqC81AZAwEUPew==} engines: {bun: '>=1.2.0', deno: '>=2.3.0', node: '>=20.0.0'} @@ -1199,6 +1280,27 @@ snapshots: dependencies: jsbi: 4.3.2 + '@logtape/drizzle-orm@2.2.2(@logtape/logtape@2.3.0-dev.840)': + dependencies: + '@logtape/logtape': 2.3.0-dev.840 + + '@logtape/graphql-yoga@2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840)(graphql-yoga@5.21.2(graphql@16.14.2))(graphql@16.14.2)': + dependencies: + '@logtape/logtape': 2.3.0-dev.840 + graphql: 16.14.2 + graphql-yoga: 5.21.2(graphql@16.14.2) + + '@logtape/logtape@2.3.0-dev.840': {} + + '@logtape/testing-node@2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840)': + dependencies: + '@logtape/logtape': 2.3.0-dev.840 + '@logtape/testing': 2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840) + + '@logtape/testing@2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840)': + dependencies: + '@logtape/logtape': 2.3.0-dev.840 + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': dependencies: '@emnapi/core': 1.11.0 @@ -1208,6 +1310,13 @@ snapshots: '@optique/core@1.1.0': {} + '@optique/core@1.1.1': {} + + '@optique/logtape@1.1.1(@logtape/logtape@2.3.0-dev.840)': + dependencies: + '@logtape/logtape': 2.3.0-dev.840 + '@optique/core': 1.1.1 + '@optique/run@1.1.0': dependencies: '@optique/core': 1.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cb61ad7..f14ebc0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,9 +6,20 @@ allowBuilds: catalog: "@electric-sql/pglite": ^0.5.3 + "@logtape/drizzle-orm": ^2.2.2 + "@logtape/graphql-yoga": 2.3.0-dev.840 + "@logtape/logtape": 2.3.0-dev.840 + "@logtape/testing-node": 2.3.0-dev.840 + "@optique/logtape": ^1.1.1 "@types/node": ^26.0.0 "@types/pg": ^8.20.0 drizzle-orm: 1.0.0-beta.22 pg: ^8.21.0 tsdown: ^0.22.3 typescript: ^6.0.3 + +minimumReleaseAgeExclude: + - "@logtape/graphql-yoga@2.3.0-dev.840" + - "@logtape/logtape@2.3.0-dev.840" + - "@logtape/testing-node@2.3.0-dev.840" + - "@logtape/testing@2.3.0-dev.840" diff --git a/scripts/dev.mts b/scripts/dev.mts index 3623da8..877f306 100644 --- a/scripts/dev.mts +++ b/scripts/dev.mts @@ -247,15 +247,29 @@ try { await waitForBuilds(buildExit); + const serverArgs: string[] = [ + "--watch", + "bin/drfed-server.mjs", + "--pglite-data-path", + "../../.pgdata", + "--listen=0.0.0.0:8888", + ]; + + const logLevel = process.env.usage_log_level; + if (logLevel != null && logLevel !== "") { + serverArgs.push("--log-level", logLevel); + } else { + serverArgs.push("--log-level", "debug"); + } + + const logOutput = process.env.usage_log_output; + if (logOutput != null && logOutput !== "") { + serverArgs.push("--log-output", logOutput); + } + serverProcess = spawnManaged( process.execPath, - [ - "--watch", - "bin/drfed-server.mjs", - "--pglite-data-path", - "../../.pgdata", - "--listen=0.0.0.0:8888", - ], + serverArgs, join(root, "packages", "drfed"), ); const serverExit = await waitForExit(serverProcess);