fix(integrations): keep unifi-os-types client-safe
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
277fbb841e
commit
3125bcf400
5 files changed
+42
-20
No files matched your search
@@ -1,4 +1,7 @@
|
||||
// @vitest-environment node
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
vi.hoisted(() => {
|
||||
@@ -147,6 +150,22 @@ describe("matchCloudSite", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("client-safety", () => {
|
||||
// `unifi-os-types.ts` is re-exported by `packages/integrations/src/types.ts`,
|
||||
// which client components import (e.g. via the spotlight search group). An
|
||||
// import of `@homarr/common/server` here drags `ioredis` — and transitively
|
||||
// Node's `dns` module — into the browser bundle and breaks `pnpm dev`/build.
|
||||
// Only a source-text check catches this: a type-only "does it compile" check
|
||||
// would not, since the breakage is a bundler resolution failure, not a type
|
||||
// error. This regression guard is what would have caught the original bug.
|
||||
test("does not import @homarr/common/server", () => {
|
||||
const path = fileURLToPath(new URL("../unifi-os-types.ts", import.meta.url));
|
||||
const source = readFileSync(path, "utf-8");
|
||||
|
||||
expect(source).not.toContain("@homarr/common/server");
|
||||
});
|
||||
});
|
||||
|
||||
describe("unifiOsClientSchema", () => {
|
||||
test("keeps fields the published OpenAPI does not document", () => {
|
||||
const parsed = unifiOsClientSchema.parse({
|
||||
|
||||
@@ -2,11 +2,11 @@ import { ResponseError } from "@homarr/common/server";
|
||||
import type { RequestInit, Response } from "undici";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { parseUnifiOsAsync } from "./unifi-os-parse";
|
||||
import type { UnifiOsClientCounts, UnifiOsDevice, UnifiOsDeviceStatistics, UnifiOsSite } from "./unifi-os-types";
|
||||
import {
|
||||
countClients,
|
||||
createUnifiOsPageSchema,
|
||||
parseUnifiOsAsync,
|
||||
unifiOsClientSchema,
|
||||
unifiOsDeviceSchema,
|
||||
unifiOsDeviceStatisticsSchema,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ParseError } from "@homarr/common/server";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
/**
|
||||
* Server-only: pulled out of `unifi-os-types.ts` because `ParseError` comes
|
||||
* from `@homarr/common/server`, which drags in `ioredis` and Node's `dns`.
|
||||
* `unifi-os-types.ts` is re-exported by `packages/integrations/src/types.ts`,
|
||||
* which client components import, so it must stay free of server-only code.
|
||||
*/
|
||||
export const parseUnifiOsAsync = async <TOutput>(
|
||||
value: unknown,
|
||||
schema: z.ZodType<TOutput>,
|
||||
message: string,
|
||||
): Promise<TOutput> => {
|
||||
const result = await schema.safeParseAsync(value);
|
||||
if (!result.success) {
|
||||
throw new ParseError(message, { cause: result.error });
|
||||
}
|
||||
return result.data;
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ParseError } from "@homarr/common/server";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
/**
|
||||
@@ -153,18 +152,6 @@ export interface UnifiOsSummary {
|
||||
gateway: UnifiOsGateway | null;
|
||||
}
|
||||
|
||||
export const parseUnifiOsAsync = async <TOutput>(
|
||||
value: unknown,
|
||||
schema: z.ZodType<TOutput>,
|
||||
message: string,
|
||||
): Promise<TOutput> => {
|
||||
const result = await schema.safeParseAsync(value);
|
||||
if (!result.success) {
|
||||
throw new ParseError(message, { cause: result.error });
|
||||
}
|
||||
return result.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* L'API locale renvoie `aa:bb:cc:11:22:33`, le cloud peut renvoyer une autre
|
||||
* casse ou d'autres séparateurs. La comparaison se fait sur une forme canonique.
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { UnifiOsFetch } from "./unifi-os-api";
|
||||
import { parseUnifiOsAsync } from "./unifi-os-parse";
|
||||
import type { UnifiOsIspMetrics } from "./unifi-os-types";
|
||||
import {
|
||||
matchCloudSite,
|
||||
parseUnifiOsAsync,
|
||||
unifiSmIspMetricsResponseSchema,
|
||||
unifiSmSiteSchema,
|
||||
} from "./unifi-os-types";
|
||||
import { matchCloudSite, unifiSmIspMetricsResponseSchema, unifiSmSiteSchema } from "./unifi-os-types";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
const logger = createLogger({ module: "unifiSmApi" });
|
||||
|
||||
Reference in new issue
Block a user