diff --git a/package.json b/package.json index 7697e1d8005..58ffa103077 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "private": true, "type": "module", "scripts": { - "preflight:workspace-links": "pnpm exec tsx scripts/ensure-workspace-package-links.ts", "dev": "pnpm --filter @paperclipai/server exec tsx ../scripts/dev-runner.ts watch", "dev:watch": "pnpm --filter @paperclipai/server exec tsx ../scripts/dev-runner.ts watch", "dev:once": "pnpm --filter @paperclipai/server exec tsx ../scripts/dev-runner.ts dev", @@ -11,10 +10,10 @@ "dev:stop": "pnpm --filter @paperclipai/server exec tsx ../scripts/dev-service.ts stop", "dev:server": "pnpm --filter @paperclipai/server dev", "dev:ui": "pnpm --filter @paperclipai/ui dev", - "build": "pnpm run preflight:workspace-links && pnpm -r build", - "typecheck": "pnpm run preflight:workspace-links && pnpm -r typecheck", - "test": "pnpm run preflight:workspace-links && vitest", - "test:run": "pnpm run preflight:workspace-links && vitest run", + "build": "pnpm -r build", + "typecheck": "pnpm -r typecheck", + "test": "vitest", + "test:run": "vitest run", "db:generate": "pnpm --filter @paperclipai/db generate", "db:migrate": "pnpm --filter @paperclipai/db migrate", "secrets:migrate-inline-env": "tsx scripts/migrate-inline-env-secrets.ts", diff --git a/packages/shared/src/telemetry/client.ts b/packages/shared/src/telemetry/client.ts index 9c4181cd3a1..c12cc8dfebf 100644 --- a/packages/shared/src/telemetry/client.ts +++ b/packages/shared/src/telemetry/client.ts @@ -6,10 +6,7 @@ import type { TelemetryState, } from "./types.js"; -const DEFAULT_ENDPOINTS = [ - "https://telemetry.paperclip.ing/ingest", - "https://rusqrrg391.execute-api.us-east-1.amazonaws.com/ingest", -] as const; +const DEFAULT_ENDPOINT = "https://telemetry.paperclip.ing/ingest"; const BATCH_SIZE = 50; const SEND_TIMEOUT_MS = 5_000; @@ -47,35 +44,28 @@ export class TelemetryClient { const events = this.queue.splice(0); const state = this.getState(); - const endpoints = this.resolveEndpoints(); + const endpoint = this.config.endpoint ?? DEFAULT_ENDPOINT; const app = this.config.app ?? "paperclip"; const schemaVersion = this.config.schemaVersion ?? "1"; - const body = JSON.stringify({ - app, - schemaVersion, - installId: state.installId, - version: this.version, - events, - }); - - for (const endpoint of endpoints) { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS); - try { - const response = await fetch(endpoint, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body, - signal: controller.signal, - }); - if (response.ok) { - return; - } - } catch { - // Try the next built-in endpoint before dropping the batch. - } finally { - clearTimeout(timer); - } + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS); + try { + await fetch(endpoint, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + app, + schemaVersion, + installId: state.installId, + version: this.version, + events, + }), + signal: controller.signal, + }); + } catch { + // Fire-and-forget: silent failure, no retries + } finally { + clearTimeout(timer); } } @@ -111,9 +101,4 @@ export class TelemetryClient { } return this.state; } - - private resolveEndpoints(): readonly string[] { - const configured = this.config.endpoint?.trim(); - return configured ? [configured] : DEFAULT_ENDPOINTS; - } } diff --git a/scripts/ensure-workspace-package-links.ts b/scripts/ensure-workspace-package-links.ts deleted file mode 100644 index 17a99909458..00000000000 --- a/scripts/ensure-workspace-package-links.ts +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env -S node --import tsx -import fs from "node:fs/promises"; -import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs"; -import path from "node:path"; -import { repoRoot } from "./dev-service-profile.ts"; - -type WorkspaceLinkMismatch = { - workspaceDir: string; - packageName: string; - expectedPath: string; - actualPath: string | null; -}; - -function readJsonFile(filePath: string): Record { - return JSON.parse(readFileSync(filePath, "utf8")) as Record; -} - -function discoverWorkspacePackagePaths(rootDir: string): Map { - const packagePaths = new Map(); - const ignoredDirNames = new Set([".git", ".paperclip", "dist", "node_modules"]); - - function visit(dirPath: string) { - const packageJsonPath = path.join(dirPath, "package.json"); - if (existsSync(packageJsonPath)) { - const packageJson = readJsonFile(packageJsonPath); - if (typeof packageJson.name === "string" && packageJson.name.length > 0) { - packagePaths.set(packageJson.name, dirPath); - } - } - - for (const entry of readdirSync(dirPath, { withFileTypes: true })) { - if (!entry.isDirectory()) continue; - if (ignoredDirNames.has(entry.name)) continue; - visit(path.join(dirPath, entry.name)); - } - } - - visit(path.join(rootDir, "packages")); - visit(path.join(rootDir, "server")); - visit(path.join(rootDir, "ui")); - visit(path.join(rootDir, "cli")); - - return packagePaths; -} - -function isLinkedGitWorktreeCheckout(rootDir: string) { - const gitMetadataPath = path.join(rootDir, ".git"); - if (!existsSync(gitMetadataPath)) return false; - - const stat = lstatSync(gitMetadataPath); - if (!stat.isFile()) return false; - - return readFileSync(gitMetadataPath, "utf8").trimStart().startsWith("gitdir:"); -} - -if (!isLinkedGitWorktreeCheckout(repoRoot)) { - process.exit(0); -} - -const workspacePackagePaths = discoverWorkspacePackagePaths(repoRoot); -const workspaceDirs = Array.from( - new Set( - Array.from(workspacePackagePaths.values()) - .map((packagePath) => path.relative(repoRoot, packagePath)) - .filter((workspaceDir) => workspaceDir.length > 0), - ), -).sort(); - -function findWorkspaceLinkMismatches(workspaceDir: string): WorkspaceLinkMismatch[] { - const packageJson = readJsonFile(path.join(repoRoot, workspaceDir, "package.json")); - const dependencies = { - ...(packageJson.dependencies as Record | undefined), - ...(packageJson.devDependencies as Record | undefined), - }; - const mismatches: WorkspaceLinkMismatch[] = []; - - for (const [packageName, version] of Object.entries(dependencies)) { - if (typeof version !== "string" || !version.startsWith("workspace:")) continue; - - const expectedPath = workspacePackagePaths.get(packageName); - if (!expectedPath) continue; - - const linkPath = path.join(repoRoot, workspaceDir, "node_modules", ...packageName.split("/")); - const actualPath = existsSync(linkPath) ? path.resolve(realpathSync(linkPath)) : null; - if (actualPath === path.resolve(expectedPath)) continue; - - mismatches.push({ - workspaceDir, - packageName, - expectedPath: path.resolve(expectedPath), - actualPath, - }); - } - - return mismatches; -} - -async function ensureWorkspaceLinksCurrent(workspaceDir: string) { - const mismatches = findWorkspaceLinkMismatches(workspaceDir); - if (mismatches.length === 0) return; - - console.log(`[paperclip] detected stale workspace package links for ${workspaceDir}; relinking dependencies...`); - for (const mismatch of mismatches) { - console.log( - `[paperclip] ${mismatch.packageName}: ${mismatch.actualPath ?? "missing"} -> ${mismatch.expectedPath}`, - ); - } - - for (const mismatch of mismatches) { - const linkPath = path.join(repoRoot, mismatch.workspaceDir, "node_modules", ...mismatch.packageName.split("/")); - await fs.mkdir(path.dirname(linkPath), { recursive: true }); - await fs.rm(linkPath, { recursive: true, force: true }); - await fs.symlink(mismatch.expectedPath, linkPath); - } - - const remainingMismatches = findWorkspaceLinkMismatches(workspaceDir); - if (remainingMismatches.length === 0) return; - - throw new Error( - `Workspace relink did not repair all ${workspaceDir} package links: ${remainingMismatches.map((item) => item.packageName).join(", ")}`, - ); -} - -for (const workspaceDir of workspaceDirs) { - await ensureWorkspaceLinksCurrent(workspaceDir); -} diff --git a/server/src/__tests__/telemetry-client-flush.test.ts b/server/src/__tests__/telemetry-client-flush.test.ts index 85826e443c4..2264638c2fc 100644 --- a/server/src/__tests__/telemetry-client-flush.test.ts +++ b/server/src/__tests__/telemetry-client-flush.test.ts @@ -76,21 +76,6 @@ describe("TelemetryClient periodic flush", () => { expect(fetch).not.toHaveBeenCalled(); }); - it("falls back to the api gateway ingest url when the default hostname fails", async () => { - vi.mocked(fetch) - .mockRejectedValueOnce(new TypeError("getaddrinfo ENOTFOUND telemetry.paperclip.ing")) - .mockResolvedValueOnce({ ok: true }); - - const client = makeClient({ endpoint: undefined }); - client.track("install.started"); - - await client.flush(); - - expect(fetch).toHaveBeenCalledTimes(2); - expect(vi.mocked(fetch).mock.calls[0]?.[0]).toBe("https://telemetry.paperclip.ing/ingest"); - expect(vi.mocked(fetch).mock.calls[1]?.[0]).toBe("https://rusqrrg391.execute-api.us-east-1.amazonaws.com/ingest"); - }); - it("startPeriodicFlush is idempotent", () => { const client = makeClient(); client.startPeriodicFlush(1000);