mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
Remove telemetry fallback and root preflight hooks
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
d03efda207
commit
5f80aa9aa0
4 files changed
+25
-182
No files matched your search
+4
-5
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<string, unknown> {
|
||||
return JSON.parse(readFileSync(filePath, "utf8")) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function discoverWorkspacePackagePaths(rootDir: string): Map<string, string> {
|
||||
const packagePaths = new Map<string, string>();
|
||||
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<string, unknown> | undefined),
|
||||
...(packageJson.devDependencies as Record<string, unknown> | 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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user