Skip to content

feat: tolgee platform client#3201

Open
stepan662 wants to merge 7 commits into
mainfrom
stepangranat/tolgee-client-lib
Open

feat: tolgee platform client#3201
stepan662 wants to merge 7 commits into
mainfrom
stepangranat/tolgee-client-lib

Conversation

@stepan662
Copy link
Copy Markdown
Contributor

@stepan662 stepan662 commented Aug 13, 2025

Summary by CodeRabbit

  • New Features

    • Introduced the @tginternal/client package: a typed API client with API-key and user-token auth, improved error messages, utilities for key info and unresolved-translation reporting, and a consolidated public API.
    • Automated publish workflow to release the client package on demand and after releases.
  • Chores

    • Added build/config tooling, version generation, and package manifest for the client.
    • Expanded CI to include client build/tests.

Additions on top of the original client package

This branch also lands typed webhooks so anything consuming @tginternal/client (apps, plugins, custom webhook receivers) can type-check webhook payloads end-to-end without hand-maintaining shapes.

Backend — OpenAPI 3.1 webhooks section

  • application.yaml now emits OpenAPI 3.1 (springdoc.api-docs.version=openapi_3_1).
  • New AppWebhookOpenApiCustomizer injects a webhooks entry per visible ActivityType into the "All Internal" group. Per-event payload schemas honour the three size-control flags on ActivityType:
    • onlyCountsInList=truemodifiedEntities is constrained to {}; signal moves to counts and params.
    • restrictEntitiesInList=[X::class, …]modifiedEntities is restricted to those entity classes.
    • paramsProvider=… → a typed params property appears on activityData.
  • Per-entity <EntityClass>Modifications schemas (TranslationModifications, KeyModifications, …) are reflectively built from @ActivityLoggedProp annotations on each @ActivityLoggedEntity.
  • OpenApiUnusedSchemaCleaner now also walks openAPI.webhooks so synthesized payload schemas survive the cleanup pass.

Tests

  • WebhookSpecGenerationTest — 214 parameterized cases prove the spec is well-formed: every visible ActivityType has a webhook entry with a $ref to a payload schema, payload schemas honour the three flag-driven shape rules, and per-entity Modifications schemas mirror their @ActivityLoggedProp annotations.
  • WebhookContractFixture + 4 contract test classes — prove the wire payload matches the spec. Pattern: per-event test class with one trigger method calling the real controller/service path, then fixture.waitForWebhookWithType(event) + fixture.assertConformsTo(payload, event). No abstract base class (team convention) — every helper is called via an explicit fixture.X(…) so origin is grep-able from the call site.
    • SetTranslationsWebhookContractTest — full-data, single entity.
    • KeyNameEditWebhookContractTest — full-data + asserts the modifications.name.{old,new} diff.
    • CreateLanguageWebhookContractTest — full-data, Language only.
    • DeleteLanguageWebhookContractTest — proves restrictEntitiesInList = [Language::class] is honoured even when the cascade deletes translations.

Client (@tginternal/client)

  • openapi-typescript is already on ^7.9.1 here (vs the webapp's 5.x), so the generated src/schema.generated.ts carries the webhooks namespace automatically.
  • README documents the consumer pattern:
    import type { webhooks } from "@tginternal/client";
    type SetTranslationsPayload =
      webhooks["SET_TRANSLATIONS"]["post"]["requestBody"]["content"]["application/json"];
  • Discriminated-union usage via activityData.type is documented too, plus the HMAC verification recipe for the Tolgee-Signature header.

Deferred (clean follow-ups)

  • Contract tests for IMPORT (counts-only — needs ImportService.import(...) driver), BATCH_MACHINE_TRANSLATE (counts-only + paramsProvider@Import(BatchJobBaseConfiguration::class) interacts oddly with @MockitoBean wiring; needs its own debugging session), and BRANCH_MERGE (EE-only). The pattern is established — each is a copy-paste-and-swap of an existing class.
  • Webapp's openapi-typescript 5.4.2 → 7.x bump (~40 useApiQuery/useApiMutation call sites need updates due to operation-type shape changes). Webapp's 5.x still tolerates 3.1 input for the schema it consumes; bump can ship as its own scope.

Versions published

  • @tginternal/client@0.0.0-prerelease.c8329b552 (workflow_dispatch on this branch)
  • Additional docs commit on top — next workflow_dispatch will publish …4ca8a9a4.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 13, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new TypeScript client package @tginternal/client (API client, schema utils, tests, build) and CI changes: a GitHub Actions workflow to publish the client to npm and additions to the repo test workflow to run client checks.

Changes

Client package implementation

Layer / File(s) Summary
Package metadata, build, and versioning
client/package.json, client/tsconfig.json, client/vite.config.js, client/.gitignore, client/scripts/updateVersion.js, client/src/version.ts
New package manifest and build config: ESM/CJS exports, publish files, dev/build/test scripts, strict TypeScript config emitting declarations to lib, Vite library build, updateVersion script that sets package.json.version and generates src/version.ts.
API client core and public surface
client/src/ApiClient.ts, client/src/constants.ts, client/src/main.ts
Implements createApiClient with ApiClientProps, auth header injection (user token or x-api-key), request/response hooks, logging (USER_AGENT/VERSION), login helper, projectIdFromKey, and exported types; main.ts re-exports public modules.
Error formatting and conflict printing
client/src/errorFromLoadable.ts, client/src/printFailedKeys.ts
Adds errorFromLoadable and handleLoadableError to map HTTP statuses and API payloads into readable messages; adds utilities to render and print unresolved translation conflict messages.
Schema-driven types and API key info
client/src/schema.utils.ts, client/src/getApiKeyInformation.ts, client/src/schema.generated.ts
Exports generic types to extract OpenAPI path/method/query/body/response shapes; adds getApiKeyInformation to fetch and normalize PAT/PAK metadata (username, project, expiry) via API calls.
Tests
client/src/ApiClient.test.ts
Unit tests for ApiClient creation, projectIdFromKey parsing, and basic behavior assertions.

CI / Workflow updates

Layer / File(s) Summary
Publish client workflow
.github/workflows/publish-client.yml
New GitHub Actions workflow "Publish client" triggered by manual dispatch, the Release workflow completion on main, and PRs. Checks out repo, sets Node 22.x, runs npm ci and npm run release-dry, derives VERSION from .VERSION (or composes prerelease using commit SHA), starts backend (via retry step), runs npm auth and ./client install/schema, then conditionally runs update-version, build, test and npm publish with latest or prerelease tags after checking npm for existing versions.
Test workflow: client checks
.github/workflows/test.yml
Adds a client-checks job that installs client deps, builds, and runs client unit tests; integrates client-checks into the everything-passed aggregation and failure-collection logic.
sequenceDiagram
    actor Dev as Developer (build/publish)
    participant GH as GitHub Actions
    participant Backend as Backend Service
    participant Client as `@tginternal/client`
    participant NPM as npm Registry

    Dev->>GH: dispatch publish-client workflow
    GH->>GH: checkout repo, setup Node, run release-dry
    GH->>Backend: start backend (runDockerE2e)
    GH->>Client: install deps, run schema generation
    note right of GH: compute VERSION from .VERSION or short SHA
    GH->>NPM: check if package@VERSION exists
    alt package missing
        GH->>Client: update-version, build, test
        GH->>NPM: npm publish (tag: latest|prerelease)
    else package exists
        GH-->>Dev: skip publish
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested labels

keep-open

Suggested reviewers

  • dkrizan

Poem

🐰 A tiny client hops into the wood,
Types and schemas stitched together good.
Tokens tucked in headers, errors made clear,
Publish steps ready, the registry near.
Hooray — a package bound for npm cheer!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: tolgee platform client' is clear, concise, and directly describes the main change: the introduction of a new Tolgee platform client library.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch stepangranat/tolgee-client-lib

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🔭 Outside diff range comments (1)
client/scripts/updateVersion.js (1)

1-23: Add error handling and input validation to the version update script.

The script lacks error handling for file operations and version validation, which could lead to cryptic failures or silent errors during the CI/CD process.

Apply comprehensive error handling and validation:

 import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
 
-const packageJson = fs.readFileSync("./package.json");
+// Get directory of current module
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const packageJsonPath = path.join(__dirname, "..", "package.json");
+const versionTsPath = path.join(__dirname, "..", "src", "version.ts");
 
-const version = String(process.argv[2]).replace(/^v/, "");
+// Validate version argument
+const versionArg = process.argv[2];
+if (!versionArg) {
+  console.error("Error: Version argument is required");
+  console.error("Usage: node updateVersion.js <version>");
+  process.exit(1);
+}
 
-console.log({ version });
+const version = String(versionArg).replace(/^v/, "");
+
+// Basic semver/prerelease validation
+const versionPattern = /^(\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?|prerelease\.[a-f0-9]+)$/;
+if (!versionPattern.test(version)) {
+  console.error(`Error: Invalid version format: ${version}`);
+  console.error("Expected format: x.y.z, x.y.z-tag, or prerelease.hash");
+  process.exit(1);
+}
+
+console.log(`Updating version to: ${version}`);
+
+try {
+  const packageJson = fs.readFileSync(packageJsonPath, "utf8");
 
 const replaced = packageJson
-  .toString()
   .replaceAll(/"version": ".*"/g, `"version": "${version}"`);
 
-fs.writeFileSync("./package.json", replaced);
+  fs.writeFileSync(packageJsonPath, replaced);
+  console.log(`✓ Updated ${packageJsonPath}`);
 
 const content = `
 /**
  * This file is generated automatically by \`updateVersion.js\` script
  */
 export const VERSION = "${version}";
 `.trimStart();
 
-fs.writeFileSync("./src/version.ts", content);
+  fs.writeFileSync(versionTsPath, content);
+  console.log(`✓ Generated ${versionTsPath}`);
+} catch (error) {
+  console.error(`Error updating version: ${error.message}`);
+  process.exit(1);
+}
♻️ Duplicate comments (1)
.github/workflows/publish-client.yml (1)

35-37: Publishing will fail due to private package configuration.

The workflow attempts to publish the package, but client/package.json has "private": true, which prevents npm from publishing. This mismatch will cause the workflow to fail.

🧹 Nitpick comments (21)
client/src/version.ts (1)

1-4: Strengthen generated-file header to prevent manual edits

Recommend clarifying the source and adding a "do not edit" notice to avoid accidental manual changes.

 /**
- * This file is generated automatically by `updateVersion.js` script
+ * This file is generated automatically by client/scripts/updateVersion.js.
+ * Do not edit this file manually – run the update-version script instead.
  */
client/tsconfig.json (2)

2-27: Emit declaration maps to improve consumer DX and debugging

Including .d.ts.map helps consumers navigate types in editors and improves stack traces with source maps in many IDEs.

     "declaration": true,
+    "declarationMap": true,
     "emitDeclarationOnly": true,

6-7: Sanity check: Do you really need DOM types in a Node-targeted CLI?

Including DOM and DOM.Iterable can pollute the global type space and mask Node-vs-web API differences. If this package is CLI-only, consider dropping DOM libs and explicitly adding Node types via types: ["node"].

client/.gitignore (1)

14-16: Ignore additional common build/test artifacts

Minor hardening to keep the repo clean across tools and CI.

 *.local
+
+# Build/test artifacts
+coverage
+.nyc_output
+*.tsbuildinfo
+.eslintcache
+.turbo
.github/workflows/test.yml (1)

55-55: String quotation change is functionally equivalent but inconsistent with workflow patterns.

While changing from single quotes to double quotes doesn't affect functionality (both result in the string "false"), this creates inconsistency within the workflow file where other string values and boolean-like strings typically use single quotes in GitHub Actions workflows.

Consider reverting to single quotes for consistency with typical GitHub Actions patterns:

-          node: "false"
+          node: 'false'
-          java: "false"
+          java: 'false'

Also applies to: 205-205, 254-254, 277-277, 308-308

client/src/schema.utils.ts (1)

26-28: Consider documenting why the "ak" parameter is excluded from QueryOf.

The QueryOf type specifically excludes the "ak" parameter, which appears to be an API key parameter. This business logic embedded in the type utility should be documented for future maintainers.

Add a comment explaining the exclusion:

 export type QueryOf<
   P extends Path,
   M extends MethodsOf<P>
+  // Excludes the "ak" (API key) parameter as it's handled separately by the client
 > = paths[P][M] extends { parameters: { query?: Record<string, any> } }
   ? Omit<Exclude<paths[P][M]["parameters"]["query"], undefined>, "ak">
   : void;
client/src/main.ts (1)

1-5: Consider re-exporting LoadableData and handleLoadableError if they are part of the intended public API

TolgeeClient.ts appears to expose LoadableData and handleLoadableError; if consumers are expected to use these, re-export them here to keep a single, cohesive entry point.

Proposed change:

 export {
   type TolgeeClient,
   createTolgeeClient,
   type TolgeeClientProps,
+  type LoadableData,
+  handleLoadableError,
 } from "./TolgeeClient";
client/src/constants.ts (1)

1-6: USER_AGENT label likely mismatched with the package identity

The constant currently brands requests as Tolgee-CLI while this package is the client. If this module is shipped as @tolgee/client, consider aligning the UA string (and the link) to the client package to avoid confusion in logs/analytics.

Proposed change:

-export const USER_AGENT = `Tolgee-CLI/${VERSION} (+https://github.com/tolgee/tolgee-cli)`;
+export const USER_AGENT = `Tolgee-Client/${VERSION} (+https://github.com/tolgee/tolgee-client)`;

Follow-up:

  • If this module is shared with the CLI and intentionally uses CLI branding, ignore this suggestion.
  • Ensure the code that applies USER_AGENT only does so in Node runtimes. Browsers do not allow setting the User-Agent header; attempting to set it can be silently ignored or cause issues. If needed, gate setting the header behind a runtime check.
client/src/errorFromLoadable.ts (2)

29-33: Comment label mismatch for 400

The comment says Unauthorized for status 400; that’s Bad Request.

-    // Unauthorized
+    // Bad Request
     case 400:
       return `Invalid request data ${addErrorDetails(loadable)}`;

63-65: Optional: Handle common statuses 404 and 422 for clearer messaging

Consider adding explicit handling for Not Found (404) and Unprocessable Entity (422) to improve actionable feedback.

Example:

     // Server error
     case 500:
       return `API reported a server error. Please try again later ${addErrorDetails(
         loadable
       )}`;

+    // Not Found
+    case 404:
+      return `Requested resource was not found ${addErrorDetails(loadable)}`;
+
+    // Unprocessable Entity
+    case 422:
+      return `Request could not be processed ${addErrorDetails(loadable)}`;
client/src/ExportClient.ts (2)

20-27: Optional: Remove redundant variable and tighten typing via ApiClient generics

Minor cleanup and stronger typing:

  • Use body shorthand.
  • If ApiClient supports a generic tied to parseAs, you can avoid manual casting altogether.

Potential refactor:

-    async export(req: ExportRequest) {
-      const body = { ...req, zip: true };
-      const loadable = await apiClient.POST("/v2/projects/{projectId}/export", {
-        params: { path: { projectId: apiClient.getProjectId() } },
-        body: body,
-        parseAs: "blob",
-      });
-      return { ...loadable, data: loadable.data as unknown as Blob };
-    },
+    async export(req: ExportRequest) {
+      const loadable = await apiClient.POST("/v2/projects/{projectId}/export", {
+        params: { path: { projectId: apiClient.getProjectId() } },
+        body: { ...req, zip: true },
+        parseAs: "blob",
+      });
+      return { ...loadable, data: loadable.data as Blob | undefined };
+    },

If ApiClient.POST can be declared like POST<"blob">(...): { data: Blob | undefined; ... }, that would eliminate the cast entirely.


20-36: Method name shadowing the reserved word ‘export’ can be confusing

Using export as a method name is valid, but can be confusing when imported elsewhere and when destructuring. Consider a more descriptive name like exportArchive or exportZip to clarify behavior and avoid cognitive clash with the JS export keyword.

Example:

-    async export(req: ExportRequest) {
+    async exportArchive(req: ExportRequest) {
@@
-    async exportSingle(req: SingleExportRequest) {
+    async exportSingle(req: SingleExportRequest) {
       return apiClient.POST("/v2/projects/{projectId}/export", {
         params: { path: { projectId: apiClient.getProjectId() } },
         body: { ...req, zip: false },
       });
     },
client/src/ImportClient.ts (1)

10-13: Widen File.data type to portable Blob parts.

To support both Node and browser uniformly, accept the standard BlobPart union rather than a narrow set with Node’s Buffer. This avoids DOM/Node type friction.

Apply this diff:

-export type File = { name: string; data: string | Buffer | Blob };
+export type File = {
+  name: string;
+  // BlobPart = string | Blob | BufferSource (Uint8Array/ArrayBuffer)
+  data: string | Blob | ArrayBuffer | Uint8Array | Buffer;
+};
client/src/printFailedKeys.ts (2)

8-12: Simplify nested template string.

The nested template adds noise. It can be simplified without changing output.

Apply this diff:

-  const namespace = key.namespace ? ` ${`(namespace: ${key.namespace})`}` : "";
+  const namespace = key.namespace ? ` (namespace: ${key.namespace})` : "";

18-36: Avoid leading/trailing blank lines and use Array.some().

Starting the message with an empty string forces a leading newline; likewise, extra pushes add trailing newlines. Also, some() is clearer than find() for a boolean.

Apply this diff:

-  const someOverridable = Boolean(translations.find((c) => c.isOverridable));
-  const result = [""];
+  const someOverridable = translations.some((c) => c.isOverridable);
+  const result: string[] = [];

@@
-  result.push("");
+  // spacer before hint when present
@@
-    result.push("");
+    // trailing newline not necessary; caller can add if desired
client/src/ApiClient.ts (3)

87-98: Minor: avoid duplicate header lookups.

Reuse the apiVersion variable to prevent two header reads.

Apply this diff:

-      const apiVersion = response.headers.get("x-tolgee-version");
-      if (apiVersion) {
-        responseText += ` [${response.headers.get("x-tolgee-version")}]`;
-      }
+      const apiVersion = response.headers.get("x-tolgee-version");
+      if (apiVersion) {
+        responseText += ` [${apiVersion}]`;
+      }

9-16: parseResponse signature should accept undefined parseAs; currently typed too narrowly.

options.parseAs may be undefined. Widen the parameter type to avoid type errors. No runtime change.

Apply this diff:

-async function parseResponse(response: Response, parseAs: ParseAs) {
+async function parseResponse(response: Response, parseAs?: ParseAs) {
@@
-    if (parseAs === "stream") {
+    if (parseAs === "stream") {
       return { data: response.body, response };
     }
     return { data: await response[parseAs](), response };

Also applies to: 28-36, 101-106


119-121: Include verbose in getSettings to reflect full configuration.

Minor completeness nit.

Apply this diff:

-    getSettings(): ApiClientProps {
-      return { baseUrl, apiKey, projectId, autoThrow };
-    },
+    getSettings(): ApiClientProps {
+      return { baseUrl, apiKey, projectId, autoThrow, verbose };
+    },
client/src/getApiKeyInformation.ts (3)

1-1: Import openapi-fetch type-only to avoid unnecessary runtime dependency.

This import is used only for typing; make it type-only so TS erases it at emit.

Apply this diff:

-import createClient from "openapi-fetch";
+import type createClient from "openapi-fetch";

5-5: Break the ApiClient ↔ TolgeeClient ↔ getApiKeyInformation import cycle.

Importing handleLoadableError from TolgeeClient creates a runtime cycle: ApiClient -> getApiKeyInformation -> TolgeeClient -> ApiClient. Move/export handleLoadableError from errorFromLoadable.ts (pure utility) and import it here to eliminate the cycle.

Apply this diff:

-import { handleLoadableError } from "./TolgeeClient.js";
+import { handleLoadableError } from "./errorFromLoadable";

Then add the handleLoadableError export to errorFromLoadable.ts (see TolgeeClient comment for snippet). Keep import paths consistent (avoid mixing “.js” suffix in TS sources unless using bundler moduleResolution).


58-77: LGTM for PAT branch; consider guarding for unexpected schema changes.

Accessing info.user.name || info.user.username assumes user is always present. If the schema evolves, a nullish coalescing with a fallback (like "") would be safer, but this is acceptable if the schema is stable.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5dd63a and db0231c.

⛔ Files ignored due to path filters (1)
  • client/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • .github/workflows/publish-client.yml (1 hunks)
  • .github/workflows/test.yml (5 hunks)
  • client/.gitignore (1 hunks)
  • client/package.json (1 hunks)
  • client/scripts/updateVersion.js (1 hunks)
  • client/src/ApiClient.ts (1 hunks)
  • client/src/ExportClient.ts (1 hunks)
  • client/src/ImportClient.ts (1 hunks)
  • client/src/TolgeeClient.ts (1 hunks)
  • client/src/constants.ts (1 hunks)
  • client/src/errorFromLoadable.ts (1 hunks)
  • client/src/getApiKeyInformation.ts (1 hunks)
  • client/src/main.ts (1 hunks)
  • client/src/pathToPosix.ts (1 hunks)
  • client/src/printFailedKeys.ts (1 hunks)
  • client/src/schema.utils.ts (1 hunks)
  • client/src/version.ts (1 hunks)
  • client/src/vite-env.d.ts (1 hunks)
  • client/tsconfig.json (1 hunks)
  • client/vite.config.js (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
client/src/ExportClient.ts (2)
client/src/schema.utils.ts (1)
  • BodyOf (30-35)
client/src/ApiClient.ts (1)
  • ApiClient (125-125)
client/src/constants.ts (1)
client/src/version.ts (1)
  • VERSION (4-4)
client/src/ImportClient.ts (3)
client/src/schema.utils.ts (1)
  • BodyOf (30-35)
client/src/ApiClient.ts (1)
  • ApiClient (125-125)
client/src/pathToPosix.ts (1)
  • pathToPosix (3-5)
client/src/getApiKeyInformation.ts (3)
client/src/schema.generated.ts (1)
  • paths (6-5108)
client/src/constants.ts (1)
  • API_KEY_PAK_PREFIX (4-4)
client/src/TolgeeClient.ts (1)
  • handleLoadableError (22-26)
client/src/printFailedKeys.ts (1)
client/src/schema.generated.ts (1)
  • components (5110-9081)
client/src/errorFromLoadable.ts (1)
client/src/printFailedKeys.ts (1)
  • getUnresolvedConflictsMessage (14-37)
client/src/ApiClient.ts (4)
client/src/constants.ts (2)
  • API_KEY_PAK_PREFIX (4-4)
  • USER_AGENT (6-6)
client/src/schema.generated.ts (1)
  • paths (6-5108)
client/src/errorFromLoadable.ts (1)
  • errorFromLoadable (27-66)
client/src/getApiKeyInformation.ts (1)
  • getApiKeyInformation (29-78)
client/src/schema.utils.ts (1)
client/src/schema.generated.ts (1)
  • paths (6-5108)
🪛 actionlint (1.7.7)
.github/workflows/publish-client.yml

18-18: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: E2E testing ‍🔎 (15, 14)
  • GitHub Check: E2E testing ‍🔎 (15, 12)
  • GitHub Check: E2E testing ‍🔎 (15, 13)
  • GitHub Check: E2E testing ‍🔎 (15, 5)
  • GitHub Check: E2E testing ‍🔎 (15, 11)
  • GitHub Check: E2E testing ‍🔎 (15, 6)
  • GitHub Check: E2E testing ‍🔎 (15, 8)
  • GitHub Check: E2E testing ‍🔎 (15, 2)
  • GitHub Check: E2E testing ‍🔎 (15, 4)
  • GitHub Check: E2E testing ‍🔎 (15, 3)
  • GitHub Check: BT ‍🔎 (ee-test:test)
  • GitHub Check: BT ‍🔎 (security:test)
  • GitHub Check: BT ‍🔎 (server-app:runContextRecreatingTests)
  • GitHub Check: BT ‍🔎 (ktlint:test)
  • GitHub Check: BT ‍🔎 (data:test)
  • GitHub Check: BT ‍🔎 (server-app:runStandardTests)
  • GitHub Check: BT ‍🔎 (server-app:runWebsocketTests)
  • GitHub Check: BT ‍🔎 (server-app:runWithoutEeTests)
  • GitHub Check: Frontend static check 🪲
🔇 Additional comments (8)
client/tsconfig.json (1)

2-27: Verified TypeScript version supports the chosen strict compiler options

  • client/package.json: typescript devDependency is ~5.8.3
  • client/tsconfig.json enables noUncheckedSideEffectImports and erasableSyntaxOnly (both added in TS 5.5+)

Since ~5.8.3 ≥ 5.5, these options are supported—no changes required.

.github/workflows/publish-client.yml (1)

22-22: Missing NPM_TOKEN secret will cause publishing to fail.

The workflow writes NPM_TOKEN to .npmrc but doesn't verify if the secret exists, which will result in an invalid auth token and publishing failure.

Add validation and use GitHub Actions environment variable syntax:

-      - run: echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > ~/.npmrc
+      - name: Configure npm authentication
+        run: |
+          if [ -z "${{ secrets.NPM_TOKEN }}" ]; then
+            echo "::error::NPM_TOKEN secret is not configured"
+            exit 1
+          fi
+          echo '//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}' > ~/.npmrc
+        env:
+          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
client/src/schema.utils.ts (1)

1-49: Well-structured type utilities for OpenAPI schema extraction.

The type utilities are well-designed, providing clean abstractions for extracting typed information from the generated OpenAPI schema. The conditional types and proper handling of undefined values demonstrate good TypeScript practices.

client/vite.config.js (1)

1-6: Verify ESM config compatibility

vite.config.js uses native ESM syntax. Ensure the workspace/package is configured with "type": "module" (or rename to vite.config.mjs) so Node can load this file without transpilation.

client/src/TolgeeClient.ts (1)

15-19: Nice composition of sub-clients on top of the base ApiClient.

Merging the base apiClient with namespaced sub-clients is clean and extensible.

client/src/ImportClient.ts (1)

40-44: ProjectId resolution: consider explicit projectId override or validation.

apiClient.getProjectId() can be undefined with PAT keys (see ApiClient review). Either allow passing projectId explicitly into this call or ensure getProjectId throws with a clear message if not available.

Would you like me to add an optional projectId?: number parameter to import() to override when a PAT is used?

client/src/ApiClient.ts (1)

77-81: Consider omitting user-agent header in browsers.

Browsers forbid setting User-Agent; this will be ignored (or could warn). If this package is intended for browser use, gate the header behind a runtime check.

Do you target browsers with this client? If yes, I can add a small isBrowser guard to skip setting the header.

client/src/getApiKeyInformation.ts (1)

33-57: Good 401 handling and user-friendly messaging for PAK.

The error message that includes the API host is helpful. The rest defers to shared error handling, which keeps concerns separated.

Comment thread .github/workflows/publish-client.yml Outdated
Comment thread client/package.json Outdated
Comment thread client/scripts/updateVersion.js Outdated
Comment thread client/src/ApiClient.ts
Comment thread client/src/ApiClient.ts
Comment thread client/src/TolgeeClient.ts Outdated
Comment thread client/src/TolgeeClient.ts Outdated
Comment thread client/src/version.ts Outdated
Comment thread client/src/vite-env.d.ts Outdated
Comment thread client/vite.config.js
@stepan662 stepan662 marked this pull request as draft August 13, 2025 13:32
@stepan662 stepan662 marked this pull request as ready for review August 15, 2025 13:25
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (8)
client/src/version.ts (1)

4-4: LGTM. Keep version sync with package.json enforced in CI.

The generated constant looks good. Please ensure the version value continues to stay in lockstep with client/package.json via a CI check.

client/src/vite-env.d.ts (1)

1-1: Avoid leaking Vite ambient types from a published library

Referencing vite/client can leak Vite globals (e.g., import.meta.env) into your published .d.ts and break non-Vite consumers. Since there’s no usage of import.meta.env in client/src, remove this reference or switch to Node types.

Apply one of the following:

Option A (preferred): delete the file.

-/// <reference types="vite/client" />

Option B: switch to Node types.

-/// <reference types="vite/client" />
+/// <reference types="node" />
client/scripts/updateVersion.js (1)

7-11: Fixed implementation follows JSON-based approach correctly.

Good to see the implementation was updated to use JSON parsing and stringification instead of regex replacement, addressing the previous concern about unintended field modifications.

client/vite.config.js (1)

11-11: Library name is now valid for all build formats.

The name: "client" is a valid JavaScript identifier that will work for UMD/IIFE builds, addressing the previous concern about invalid UMD global names.

client/src/errorFromLoadable.ts (1)

21-28: Bug: Passing entire params array instead of the conflicts payload to getUnresolvedConflictsMessage

You guard on params[0] being an object, but then pass the whole array. Pass the first element (the conflicts array/object) instead.

Apply this diff:

-    additionalInfo += getUnresolvedConflictsMessage(
-      loadable.error.params as any
-    );
+    additionalInfo += getUnresolvedConflictsMessage(
+      loadable.error.params[0] as any
+    );
client/src/ApiClient.ts (3)

46-62: Good fix for browser compatibility!

The function now correctly uses TextDecoder instead of Node's Buffer, making it browser-compatible. The validation with Number.isFinite() ensures the function returns undefined for invalid IDs rather than NaN.


132-140: Good error handling for getProjectId!

The method now properly throws a descriptive error when the project ID is unavailable, providing clear guidance to users about how to fix the issue.


141-143: Add guard for missing apiKey in getApiKeyInfo.

The method uses a non-null assertion on apiKey! which will crash at runtime when no API key is configured. Add a guard to throw a clear error message.

Apply this diff to add proper error handling:

     getApiKeyInfo() {
-      return getApiKeyInformation(apiClient, apiKey!);
+      if (!apiKey) {
+        throw new Error("Cannot retrieve API key info: no apiKey configured.");
+      }
+      return getApiKeyInformation(apiClient, apiKey);
     },
🧹 Nitpick comments (11)
client/tsconfig.json (1)

6-6: Reconsider including DOM libs in a publishable Node/bundler library

Including ["DOM", "DOM.Iterable"] pulls in web types which can conflict for Node consumers. If the library is isomorphic and actually uses DOM types, keep it; otherwise drop them to avoid leaking DOM into declaration output.

-    "lib": ["ES2022", "DOM", "DOM.Iterable"],
+    "lib": ["ES2022"],
client/.gitignore (1)

10-14: LGTM: ignoring build artifacts and types

Ignoring dist and lib is appropriate for Git; make sure package.json "files" or an .npmignore includes them for npm publishing.

client/scripts/updateVersion.js (2)

5-5: Add input validation for the version argument.

The script doesn't validate that a version argument was provided, which could lead to confusing behavior if called without arguments.

-const version = String(process.argv[2]).replace(/^v/, "");
+const versionArg = process.argv[2];
+if (!versionArg) {
+  console.error("Error: Version argument is required");
+  process.exit(1);
+}
+const version = String(versionArg).replace(/^v/, "");

3-3: Consider adding error handling for file operations.

The script performs several file operations without error handling. If package.json doesn't exist or the src directory is missing, the script will crash with unclear error messages.

-const packageJson = fs.readFileSync("./package.json");
+try {
+  const packageJson = fs.readFileSync("./package.json");
+  const packageData = JSON.parse(packageJson);
+  packageData.version = version;
+  const replaced = JSON.stringify(packageData, null, 2) + "\n";
+  fs.writeFileSync("./package.json", replaced);
+  fs.writeFileSync("./src/version.ts", content);
+} catch (error) {
+  console.error("Error updating version:", error.message);
+  process.exit(1);
+}

Also applies to: 7-11, 20-20

client/vite.config.js (1)

16-18: Test configuration should use explicit test framework setup.

The globals: true configuration in Vite enables global test functions (describe, it, expect) without imports, but it's generally better practice to use explicit imports for clarity and type safety.

Consider using explicit imports in test files instead of relying on globals:

 test: {
-  globals: true,
+  globals: false,
 },

And update test files to import test functions:

import { describe, it, expect } from 'vitest';
client/src/constants.ts (1)

6-6: User agent string references incorrect project.

The user agent string mentions "Tolgee-CLI" but this appears to be a client library, not a CLI tool. The GitHub URL also points to tolgee-cli which may not be correct for this client package.

-export const USER_AGENT = `Tolgee-CLI/${VERSION} (+https://github.com/tolgee/tolgee-cli)`;
+export const USER_AGENT = `Tolgee-Client/${VERSION} (+https://github.com/tolgee/tolgee-platform)`;
client/src/errorFromLoadable.ts (2)

35-41: Misleading comments for HTTP status cases

Comment labels don't match the status codes (400 is Bad Request, 401 is Unauthorized).

-    // Unauthorized
+    // Bad Request
     case 400:
       return `Invalid request data ${addErrorDetails(loadable)}`;

-    // Unauthorized
+    // Unauthorized
     case 401:
       return `Missing or invalid authentication token ${addErrorDetails(
         loadable
       )}`;

30-31: Potential double newline when appending conflict details

getUnresolvedConflictsMessage currently returns a string starting with a newline. Combined with "\n" + additionalInfo, this produces an extra blank line. Recommend removing the leading newline from getUnresolvedConflictsMessage (see comment in printFailedKeys.ts).

client/src/printFailedKeys.ts (1)

8-12: Simplify renderKey string construction

The nested template in namespace is unnecessary and slightly harder to read.

 export function renderKey(key: PartialKey, note?: string) {
-  const namespace = key.namespace ? ` ${`(namespace: ${key.namespace})`}` : "";
-  const renderedNote = note ? ` ${note}` : "";
-  return `${key.keyName}${namespace}${renderedNote}`;
+  const namespace = key.namespace ? ` (namespace: ${key.namespace})` : "";
+  const renderedNote = note ? ` ${note}` : "";
+  return `${key.keyName}${namespace}${renderedNote}`;
 }
client/src/getApiKeyInformation.ts (2)

68-76: Add a fallback for missing PAT username to align with PAK handling

PAK path falls back to "", but PAT path omits a fallback which can yield undefined. Make both consistent.

-    const username = info.user.name || info.user.username;
+    const username = info.user.name ?? info.user.username ?? "<unknown user>";

35-41: DRY: Factor out repeated 401 handling logic

The unauthorized error handling is duplicated for both endpoints. Consider extracting a small helper for clarity and consistency.

If you’d like, I can open a small follow-up PR to introduce a shared assertAuthenticated(loadable) helper and update both branches accordingly.

Also applies to: 59-66

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c5dd63a and 3dbf1df.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • webapp/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • .github/workflows/publish-client.yml (1 hunks)
  • .github/workflows/test.yml (5 hunks)
  • client/.gitignore (1 hunks)
  • client/package.json (1 hunks)
  • client/scripts/updateVersion.js (1 hunks)
  • client/src/ApiClient.test.ts (1 hunks)
  • client/src/ApiClient.ts (1 hunks)
  • client/src/constants.ts (1 hunks)
  • client/src/errorFromLoadable.ts (1 hunks)
  • client/src/getApiKeyInformation.ts (1 hunks)
  • client/src/main.ts (1 hunks)
  • client/src/printFailedKeys.ts (1 hunks)
  • client/src/schema.utils.ts (1 hunks)
  • client/src/version.ts (1 hunks)
  • client/src/vite-env.d.ts (1 hunks)
  • client/tsconfig.json (1 hunks)
  • client/vite.config.js (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
client/src/errorFromLoadable.ts (2)
client/src/ApiClient.ts (1)
  • LoadableData (10-12)
client/src/printFailedKeys.ts (1)
  • getUnresolvedConflictsMessage (14-37)
client/src/ApiClient.test.ts (1)
client/src/ApiClient.ts (2)
  • createApiClient (74-171)
  • projectIdFromKey (46-62)
client/src/constants.ts (1)
client/src/version.ts (1)
  • VERSION (4-4)
client/src/getApiKeyInformation.ts (3)
client/src/schema.generated.ts (1)
  • paths (6-5108)
client/src/constants.ts (1)
  • API_KEY_PAK_PREFIX (4-4)
client/src/errorFromLoadable.ts (1)
  • handleLoadableError (4-8)
client/src/ApiClient.ts (5)
client/src/constants.ts (2)
  • API_KEY_PAK_PREFIX (4-4)
  • USER_AGENT (6-6)
client/src/errorFromLoadable.ts (1)
  • errorFromLoadable (33-72)
client/src/schema.generated.ts (1)
  • paths (6-5108)
client/src/getApiKeyInformation.ts (1)
  • getApiKeyInformation (29-78)
client/src/schema.utils.ts (1)
  • BodyOf (30-35)
client/src/schema.utils.ts (1)
client/src/schema.generated.ts (1)
  • paths (6-5108)
🔇 Additional comments (8)
.github/workflows/test.yml (1)

55-55: No functional change in quoting; safe to keep

Switching to double-quoted "false" keeps the value as a string (composite action inputs are strings). If ./.github/actions/setup-env relies on strict string comparison, this is fine; if it expects booleans, it would already have been receiving strings. No action needed.

If you want to be extra safe, confirm setup-env treats these inputs as strings:

  • node
  • java

Also applies to: 205-205, 254-254, 277-277, 308-308

client/tsconfig.json (1)

24-28: Emit declarations only is correct; ensure dist JS is produced by the bundler

With "emitDeclarationOnly": true and outDir: ./lib, ensure your bundler (Vite/Rollup) outputs JS to dist and that package.json points "types" to lib and "exports"/"main"/"module" to dist. No changes needed if already configured.

client/src/ApiClient.test.ts (1)

1-29: Well-structured test suite with good coverage.

The test suite effectively covers the key functionality of the API client:

  • Basic client creation and configuration
  • PAK key parsing (positive case)
  • PAT key parsing (negative case)
  • Invalid key handling

The tests are clear, focused, and use appropriate assertions.

client/src/constants.ts (1)

3-4: API key prefixes are well-defined.

The constants clearly distinguish between Personal Access Tokens (PAT) and Project API Keys (PAK), which aligns with the key parsing logic in ApiClient.ts.

client/src/main.ts (1)

1-4: Clean module aggregation pattern.

The main entry point follows a clean barrel export pattern, providing a single point of access to all public APIs from the various modules. This is a good practice for library distribution.

client/package.json (1)

1-40: Manifest looks publish-ready and consistent with dual ESM/CJS export

Exports mapping is correct, publishConfig.access is set, and the absence of "private": true aligns with the new publish workflow.

client/src/ApiClient.ts (1)

84-89: Add conditional x-api-key header to avoid "undefined" string.

Currently, the x-api-key header is only set conditionally in the middleware (lines 103), but not in the initial headers. This is correct behavior. However, if the middleware ever gets bypassed or if there are edge cases, consider being defensive here as well.

client/src/schema.utils.ts (1)

1-49: Well-structured type utilities!

The type utilities are well-designed and provide excellent type safety for the OpenAPI client. The use of conditional types and proper fallbacks to void ensures type safety even when optional fields are absent. The QueryOf type cleverly excludes the "ak" parameter, likely to prevent conflicts with the API key authentication.

Comment thread .github/workflows/publish-client.yml
Comment thread .github/workflows/publish-client.yml
Comment thread client/src/ApiClient.ts
Comment thread client/src/printFailedKeys.ts
Comment thread client/tsconfig.json Outdated
Comment thread client/tsconfig.json Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (5)
client/src/ApiClient.ts (5)

132-140: Good: getProjectId fails fast with a helpful message.

This replaces the unsafe non-null assertion and prevents leaking undefined at runtime. Clear error text too.


100-104: Good: x-api-key is set conditionally.

Avoids sending a literal "undefined" header value. Thanks for addressing this.


46-62: Harden projectIdFromKey against malformed PAKs (wrap decode/parse in try/catch).

base32Decode/TextDecoder can throw for malformed keys. Today that would crash getProjectId(). Return undefined on failures.

 export function projectIdFromKey(key: string) {
   if (!key.startsWith(API_KEY_PAK_PREFIX)) {
     return undefined;
   }

-  const keyBuffer = base32Decode(
-    key.slice(API_KEY_PAK_PREFIX.length).toUpperCase(),
-    "RFC4648"
-  );
-
-  const decoded = new TextDecoder("utf-8").decode(
-    keyBuffer as unknown as Uint8Array
-  );
-  const idStr = decoded.split("_", 1)[0];
-  const id = Number.parseInt(idStr, 10);
-  return Number.isFinite(id) ? id : undefined;
+  try {
+    const keyBuffer = base32Decode(
+      key.slice(API_KEY_PAK_PREFIX.length).toUpperCase(),
+      "RFC4648"
+    ) as unknown as Uint8Array;
+    const decoded = new TextDecoder("utf-8").decode(keyBuffer);
+    const idStr = decoded.split("_", 1)[0];
+    const id = Number.parseInt(idStr, 10);
+    return Number.isFinite(id) ? id : undefined;
+  } catch {
+    return undefined;
+  }
 }

141-143: Guard getApiKeyInfo when apiKey is missing to avoid runtime crash.

Non-null assertion can pass undefined to getApiKeyInformation, which immediately calls key.startsWith and throws.

-    getApiKeyInfo() {
-      return getApiKeyInformation(apiClient, apiKey!);
-    },
+    getApiKeyInfo() {
+      if (!apiKey) {
+        throw new Error("Cannot retrieve API key info: no apiKey configured.");
+      }
+      return getApiKeyInformation(apiClient, apiKey);
+    },

160-167: Fix Error constructor misuse in login fallback.

Error only accepts a single message argument. Passing response.error as a 2nd arg causes a TS compile error and is ignored in JS.

       } else {
-        throw new Error("Couldn't fetch access token", response.error);
+        throw new Error("Couldn't fetch access token");
       }
🧹 Nitpick comments (3)
client/src/ApiClient.ts (3)

84-89: Avoid setting User-Agent header in browsers.

Browsers disallow setting the User-Agent header and may throw “Refused to set unsafe header ‘User-Agent’”. Set it only in non-browser environments.

-  const apiClient = createClient<paths>({
-    baseUrl,
-    headers: {
-      "user-agent": USER_AGENT,
-    },
-  });
+  const apiClient = createClient<paths>({ baseUrl });

And in the request hook:

     onRequest: ({ request }) => {
       debug(`[HTTP] Requesting: ${request.method} ${request.url}`);
+      // Set user-agent only in non-browser environments
+      if (typeof window === "undefined") {
+        request.headers.set("user-agent", USER_AGENT);
+      }
       if (userToken) {
         request.headers.set("Authorization", "Bearer " + userToken);
       } else if (apiKey) {
         request.headers.set("x-api-key", apiKey);
       }
     },

Also applies to: 97-105


107-111: Nit: reuse the apiVersion variable.

Minor log cleanup; avoid redundant header lookups.

-      const apiVersion = response.headers.get("x-tolgee-version");
-      if (apiVersion) {
-        responseText += ` [${response.headers.get("x-tolgee-version")}]`;
-      }
+      const apiVersion = response.headers.get("x-tolgee-version");
+      if (apiVersion) {
+        responseText += ` [${apiVersion}]`;
+      }

14-25: Reduce body cloning/reading in parseResponse; short-circuit stream first.

Currently we clone+read text up front, even for large/streaming responses, and then parse again. Reorder to skip body reads for stream, and consult headers before falling back to reading text.

-  // handle empty content
-  // note: we return `{}` because we want user truthy checks for `.data` or `.error` to succeed
-  const clonedBody = await response.clone().text();
-  if (
-    response.status === 204 ||
-    response.headers.get("Content-Length") === "0" ||
-    !response.body ||
-    !clonedBody
-  ) {
-    return response.ok ? { data: {}, response } : { error: {}, response };
-  }
+  // short-circuit streaming responses
+  if (response.ok && parseAs === "stream") {
+    return { data: response.body, response };
+  }
+  // handle empty content (without eagerly reading the body)
+  // note: we return `{}` because we want user truthy checks for `.data` or `.error` to succeed
+  const contentLength = response.headers.get("Content-Length");
+  if (
+    response.status === 204 ||
+    contentLength === "0" ||
+    !response.body
+  ) {
+    return response.ok ? { data: {}, response } : { error: {}, response };
+  }
@@
-    // if "stream", skip parsing entirely
-    if (parseAs === "stream") {
-      return { data: response.body, response };
-    }
     return { data: await response[parseAs](), response };

Also applies to: 27-34

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3dbf1df and 334db19.

📒 Files selected for processing (2)
  • client/src/ApiClient.ts (1 hunks)
  • client/tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/tsconfig.json
🧰 Additional context used
🧬 Code Graph Analysis (1)
client/src/ApiClient.ts (5)
client/src/constants.ts (2)
  • API_KEY_PAK_PREFIX (4-4)
  • USER_AGENT (6-6)
client/src/errorFromLoadable.ts (1)
  • errorFromLoadable (33-72)
client/src/schema.generated.ts (1)
  • paths (6-5108)
client/src/getApiKeyInformation.ts (1)
  • getApiKeyInformation (29-78)
client/src/schema.utils.ts (1)
  • BodyOf (30-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: BT ‍🔎 (security:test)
  • GitHub Check: BT ‍🔎 (server-app:runWithoutEeTests)
  • GitHub Check: BT ‍🔎 (ktlint:test)
  • GitHub Check: BT ‍🔎 (server-app:runContextRecreatingTests)
  • GitHub Check: BT ‍🔎 (ee-test:test)
  • GitHub Check: BT ‍🔎 (data:test)
  • GitHub Check: BT ‍🔎 (server-app:runStandardTests)
  • GitHub Check: BT ‍🔎 (server-app:runWebsocketTests)
  • GitHub Check: Frontend static check 🪲
  • GitHub Check: Build frontend 🏗️
  • GitHub Check: publish-client

@github-actions
Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale label Oct 10, 2025
@JanCizmar JanCizmar requested review from Anty0 and dkrizan October 10, 2025 06:50
@JanCizmar
Copy link
Copy Markdown
Member

@Anty0, @dkrizan can you please get familiar with this and maybe do a CR? I think this will affect our future integration development.

Thanks @stepan662 for finishing this! 🙏

Comment thread .github/workflows/publish-client.yml
Comment thread .github/workflows/test.yml
@JanCizmar JanCizmar added enhancement New feature or request and removed stale labels Oct 10, 2025
Copy link
Copy Markdown
Member

@dkrizan dkrizan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good! Left a few minor notes.

Comment thread client/src/ApiClient.ts Outdated
Comment thread client/src/ApiClient.ts Outdated
@JanCizmar
Copy link
Copy Markdown
Member

@Anty0, can you please finish this, so we have it done?

@JanCizmar
Copy link
Copy Markdown
Member

Picking this up to address the open review feedback (so we can use this client from the upcoming Tolgee Apps SDK). The new commit (4d6add8) applies:

From @JanCizmar's review

  • publish-client.yml: trigger on the Release workflow (not Test), so publishing only happens after a successful release build.
  • test.yml: reverted Stepan's diff against main — restores buildTestEmails, the sharded matrix, SKIP_EMAIL_BUILD, CTRF reporting, etc.

From @dkrizan's review

  • Default base URL https://app.tolgee.io moved to constants.ts as DEFAULT_BASE_URL.
  • The verbose response log reuses the captured apiVersion variable instead of re-reading the header.

Outstanding CodeRabbit items

  • errorFromLoadable.ts: real bug — was passing the full params array to getUnresolvedConflictsMessage instead of params[0] (the conflicts array). Fixed.
  • ApiClient.ts#getApiKeyInfo: replaced the apiKey! non-null assertion with an explicit error when no API key is configured.
  • publish-client.yml: the version-existence check now uses npm view ...@<version> version (singular) — versions --json succeeds whenever any version of the package exists, so the original check was always reporting "already exists" once the package was published once.
  • printFailedKeys.ts: dropped the leading "" in the result buffer that combined with the caller's prepended \n to produce double spacing.
  • tsconfig.json: excluded *.test.ts from build emission so vitest/globals doesn't leak into published .d.ts. Removed the unused "node" types entry (no source imports node:*).
  • vite-env.d.ts: deleted — nothing in src/ uses Vite-specific globals (import.meta.env etc.), and the /// <reference types="vite/client" /> would have leaked Vite ambient types to consumers.

Items left as-is (please push back if you want them addressed):

  • CodeRabbit comments on TolgeeClient.ts, ExportClient.ts, ImportClient.ts, pathToPosix.ts — those files were removed from the PR after Stepan's Aug 15 batch.
  • The updateVersion.js:11 "regex too broad" comment — the current implementation uses JSON.parse/stringify and no longer relies on a regex.
  • The vite.config.js:19 "invalid UMD global name" comment — the lib name was already changed from @tolgee/client to client (valid JS identifier).

Verification

  • cd client && npm run build — green.
  • cd client && npm test — 4/4 green.
  • git diff origin/main -- .github/workflows/test.yml — empty (revert is complete).

@JanCizmar @dkrizan — could you take another look when convenient?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/test.yml:
- Around line 390-392: Add a npm script named "generate-data-cy" to
webapp/package.json (value: node ./scripts/generate-data-cy.mjs) so the CI step
that runs node ./scripts/generate-data-cy.mjs matches the workflow error
guidance; update the workflow error message instead only if you prefer
direct-invocation instructions, but ensure the "generate-data-cy" script name is
present and consistent with any messages referencing npm run generate-data-cy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e0c1edf-8ed1-469f-b574-d2ae53d3d402

📥 Commits

Reviewing files that changed from the base of the PR and between 9a376f9 and 4d6add8.

⛔ Files ignored due to path filters (1)
  • client/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • .github/workflows/publish-client.yml
  • .github/workflows/test.yml
  • client/src/ApiClient.ts
  • client/src/constants.ts
  • client/src/errorFromLoadable.ts
  • client/src/printFailedKeys.ts
  • client/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • client/tsconfig.json
  • client/src/constants.ts
  • client/src/printFailedKeys.ts
  • .github/workflows/publish-client.yml
  • client/src/errorFromLoadable.ts
  • client/src/ApiClient.ts

Comment thread .github/workflows/test.yml
Adds a new internal TypeScript client package (`@tginternal/client`)
that wraps openapi-fetch with typed paths from the platform's OpenAPI
schema, plus helpers for API-key parsing, login, and friendly error
messages. The package is versioned in lockstep with the platform and
published via a dedicated workflow (latest tag on release, prerelease
tag tied to the git short SHA otherwise).

Highlights:
- ApiClient with PAK/PAT/user-token auth, optional auto-throw, verbose
  HTTP logging, login helper, and a getApiKeyInformation that hits
  `/v2/api-keys/current` or `/v2/pats/current` depending on the key
  prefix.
- errorFromLoadable maps backend status codes and unresolved-conflict
  payloads to readable error messages.
- Schema is regenerated from the running backend during publish; the
  generated file lives at `client/src/schema.generated.ts` and is
  committed to keep type-checks reproducible without a live backend.
- New `client-checks` job in `test.yml` runs build + tests on every
  PR.

Co-Authored-By: Štěpán Granát <granat.stepan@gmail.com>
@JanCizmar JanCizmar force-pushed the stepangranat/tolgee-client-lib branch from 4d6add8 to 07ce78e Compare May 14, 2026 13:05
@JanCizmar
Copy link
Copy Markdown
Member

Force-pushed a rebased + cleaned-up version of the branch. The full PR diff against main is now just three paths: client/, .github/workflows/publish-client.yml, and a small additive change to .github/workflows/test.yml (one new client-checks job). All other diffs from the previous push (which had drifted with main moving forward) are gone.

Additional changes on top of the earlier review-feedback round:

  1. base32-decode moved to dependencies — it was imported at runtime but declared as a devDependency. Worked only because Vite's lib mode inlines deps; would have broken the moment rollupOptions.external was added.
  2. client/README.md — short install + usage doc covering createApiClient, the main helpers, and schema regeneration.
  3. schema script now reads TOLGEE_API_PORT (default 8080, so it works against a local bootRun). The publish workflow exports TOLGEE_API_PORT=8201 to match runDockerE2e.
  4. Test coverage broadened: 13 tests (up from 4) — covers getProjectId/getApiKeyInfo guard paths, errorFromLoadable status-code mapping, and getUnresolvedConflictsMessage formatting (including the params[0] bug fix from the previous round).
  5. getSettings() now returns printError so the snapshot matches ApiClientProps.
  6. exports map now includes default for forward-compat with stricter package resolvers.
  7. New client-checks job in test.yml runs npm ci && npm run build && npm test on every PR; added to everything-passed.needs so it gates the green check.

The schema port via env var is bash-only (${TOLGEE_API_PORT:-8080}), which matches the rest of tolgee's npm scripts.

Versioning is unchanged: client publishes as @tginternal/client@<platform-version> on release, 0.0.0-prerelease.<sha> otherwise (via the verifyReleaseCmd hook that writes .VERSION).

Verified locally:

  • git diff origin/main -- .github/workflows/test.yml — only the new client-checks job
  • npm run build — green
  • npm test — 13/13 green

JanCizmar added 6 commits May 14, 2026 15:15
Adopts the same pattern as tolgee-js: authenticate to npm via GitHub
Actions OIDC instead of a long-lived NPM_TOKEN secret.

- Add top-level `permissions: id-token: write, contents: read` so the
  job can mint an OIDC token.
- Configure `actions/setup-node` with `registry-url` so the npm CLI
  knows where to redeem the OIDC token.
- Drop the `~/.npmrc` echo step and the `NPM_TOKEN` env vars on both
  publish steps.
- Add `--provenance` to both `npm publish` invocations so the published
  tarballs carry build provenance attestations.

Note: requires the `@tginternal/client` package on npmjs.org to be
configured with a Trusted Publisher pointing at
`tolgee/tolgee-platform` + `publish-client.yml`.
npm trusted publishing requires npm 11.5.1+ (July 2025). Node 22.x
ships with npm 10.x and only attempts OIDC for provenance, not for
the publish PUT itself — which kept failing with a 404 ("permission
denied"). Node 24.x ships with npm 11.x and supports OIDC end-to-end.

Matches the Node version used in tolgee-js's release workflow.
npm provenance attestation requires `repository.url` to match the
GitHub repo the OIDC token was issued from. Without it, publish fails
with 422 ("Failed to validate repository information"). Added
`repository`, plus `homepage`, `bugs`, and `license` while here —
these are required to publish provenance-attested tarballs and
populate the npm package page.
- Springdoc emits OpenAPI 3.1 (springdoc.api-docs.version=openapi_3_1).
- AppWebhookOpenApiCustomizer injects a `webhooks` entry per visible
  ActivityType into the "All Internal" group's spec. Per-event
  AppWebhookPayload_<TYPE> schemas honour the three size-control flags on
  ActivityType: onlyCountsInList collapses modifiedEntities to {};
  restrictEntitiesInList narrows the allowed entity-class keys;
  paramsProvider surfaces a typed params property.
- Per-entity Modifications schemas (e.g. TranslationModifications,
  KeyModifications, LanguageModifications) are reflectively built from
  @ActivityLoggedProp annotations; non-annotated entities fall back to a
  permissive additionalProperties: PropertyModification shape so $refs
  resolve.
- Extended OpenApiUnusedSchemaCleaner to also walk openAPI.webhooks when
  collecting referenced schemas, otherwise the cleaner would strip our
  synthesized payload schemas.
- WebhookSpecGenerationTest covers 214 parameterized cases: 3.1 advertised,
  webhooks entry exists for every visible ActivityType, schema $refs land
  on existing components, counts-only/restricted/paramsProvider variants
  shape correctly, per-entity Modifications mirror the annotations.

Deferred to follow-up commits / scopes:
- WebhookPayloadSchemaContractTest (drives real activities, captures
  payloads, validates against schemas). Foundation is here; per-event
  trigger methods + parameterized matrix are next.
- Bumping webapp/openapi-typescript from 5.4.2 to 7.x. The 7.x typing of
  paths/operations breaks ~40 webapp useApiQuery/useApiMutation call sites
  because the hook helpers infer differently; non-trivial migration that
  shouldn't gate the typed-webhooks shipment. client/ already on 7.9.1.
- client/src/schema.generated.ts regeneration + vitest smoke test —
  requires running backend; plugin authors get the types as soon as
  someone runs `cd client && TOLGEE_API_PORT=8080 npm run schema` against
  a backend with these changes.
…gories

- WebhookContractFixture: injected @component (no abstract base), owns the
  WebhooksTestData fixture, mocks webhookRestTemplate, captures the JSON
  Tolgee POSTs, waits for the right activity type via
  waitForWebhookWithType(eventName), and asserts the captured payload
  conforms to the OpenAPI spec's webhook contract via assertConformsTo().
  Conformance is checked structurally (top-level fields, activityData.type
  discriminator, modifiedEntities keys ⊆ spec-declared keys) — the
  WebhookSpecGenerationTest already proves the spec shape.
- SetTranslationsWebhookContractTest: pre-creates the key (so the cascade
  doesn't pollute modifiedEntities) then sets the translation; asserts the
  payload has only Translation entries.
- KeyNameEditWebhookContractTest: creates a key, renames it; asserts the
  modifications.name.{old,new} diff is present.
- CreateLanguageWebhookContractTest: creates a language; asserts only
  Language entries.
- DeleteLanguageWebhookContractTest: creates and deletes a language;
  asserts modifiedEntities is constrained to Language (proves
  restrictEntitiesInList = [Language::class] is honoured on the wire).

Pattern is grep-able and extension-friendly: copy a test class, change the
event name + trigger method. Coverage for IMPORT (onlyCountsInList),
BATCH_MACHINE_TRANSLATE (onlyCountsInList + paramsProvider), and
BRANCH_MERGE (EE) is deferred — those need real ImportService /
BatchJobService / BranchMergeService driver setup that's worth its own
focused commit.
Plugin authors building webhook receivers can import per-event payload
types from the generated schema (e.g. webhooks["SET_TRANSLATIONS"]) and
discriminate via activityData.type. Document both the single-event and
discriminated-union patterns, plus the HMAC signature verification recipe.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants