diff --git a/.changeset/flat-parrots-tie.md b/.changeset/flat-parrots-tie.md new file mode 100644 index 0000000000..66493150b7 --- /dev/null +++ b/.changeset/flat-parrots-tie.md @@ -0,0 +1,7 @@ +--- +"@inlang/paraglide-js": minor +--- + +refactor: remove posthog-node dependency + +Posthog node has been replaced for a fetch call. Removing 3 (posthog + 2 transitive dependencies). diff --git a/packages/inlang-paraglide-js/package.json b/packages/inlang-paraglide-js/package.json index 824e77f54c..24b8c349f2 100644 --- a/packages/inlang-paraglide-js/package.json +++ b/packages/inlang-paraglide-js/package.json @@ -42,9 +42,10 @@ ], "scripts": { "dev": "vite build --mode development --watch", - "build": "vite build --mode production", + "build": "npm run env-variables && vite build --mode production", "test": "tsc --noEmit && vitest run --coverage ./src/**/*", "test:watch": "vitest --watch ./src/**/*", + "env-variables": "node ./src/services/env-variables/createIndexFile.js", "lint": "eslint ./src --fix", "format": "prettier ./src --write", "clean": "rm -rf ./dist ./node_modules", @@ -58,7 +59,6 @@ "consola": "3.2.3", "fast-glob": "^3.2.12", "json5": "2.2.3", - "posthog-node": "^4.0.1", "prettier": "^3.4.2", "prettier-plugin-jsdoc": "^1.3.0" }, diff --git a/packages/inlang-paraglide-js/src/ambient.d.ts b/packages/inlang-paraglide-js/src/ambient.d.ts deleted file mode 100644 index 96ae395494..0000000000 --- a/packages/inlang-paraglide-js/src/ambient.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * The Package version from package.json - * - * This value is inlined by vite during build to avoid having to read the package.json - * at runtime - */ -declare const PARJS_PACKAGE_VERSION: string; - -/** - * The PUBLIC_POSTHOG_TOKEN defined in the build step - */ -declare const PARJS_POSTHOG_TOKEN: string; - -/** - * The id configured in the marketplace manifest - */ -declare const PARJS_MARKTEPLACE_ID: string; diff --git a/packages/inlang-paraglide-js/src/cli/commands/compile/command.ts b/packages/inlang-paraglide-js/src/cli/commands/compile/command.ts index 53b4d13a13..b842728816 100644 --- a/packages/inlang-paraglide-js/src/cli/commands/compile/command.ts +++ b/packages/inlang-paraglide-js/src/cli/commands/compile/command.ts @@ -5,6 +5,7 @@ import { Logger } from "~/services/logger/index.js"; import { runCompiler } from "~/cli/steps/run-compiler.js"; import { DEFAULT_OUTDIR } from "~/cli/defaults.js"; import { loadProjectFromDirectory } from "@inlang/sdk"; +import { ENV_VARIABLES } from "~/services/env-variables/index.js"; export const compileCommand = new Command() .name("compile") @@ -31,7 +32,11 @@ export const compileCommand = new Command() logger.info(`Compiling inlang project at "${options.project}".`); - const project = await loadProjectFromDirectory({ path, fs }); + const project = await loadProjectFromDirectory({ + path, + fs, + appId: ENV_VARIABLES.PARJS_APP_ID, + }); await runCompiler({ project, diff --git a/packages/inlang-paraglide-js/src/cli/commands/init/command.ts b/packages/inlang-paraglide-js/src/cli/commands/init/command.ts index d1035c1267..3315a83ba7 100644 --- a/packages/inlang-paraglide-js/src/cli/commands/init/command.ts +++ b/packages/inlang-paraglide-js/src/cli/commands/init/command.ts @@ -1,7 +1,6 @@ import { Command } from "commander"; import consola from "consola"; import * as nodePath from "node:path"; -import { telemetry } from "~/services/telemetry/implementation.js"; import { Logger } from "~/services/logger/index.js"; import { findPackageJson } from "~/services/environment/package.js"; import { checkForUncommittedChanges } from "~/cli/steps/check-for-uncomitted-changes.js"; @@ -14,6 +13,7 @@ import { runCompiler } from "~/cli/steps/run-compiler.js"; import type { CliStep } from "../../utils.js"; import nodeFs from "node:fs"; import type { NodeishFilesystem } from "~/services/file-handling/types.js"; +import { ENV_VARIABLES } from "~/services/env-variables/index.js"; export const initCommand = new Command() .name("init") @@ -22,37 +22,23 @@ export const initCommand = new Command() const logger = new Logger({ silent: false, prefix: false }); logger.box("Welcome to inlang Paraglide-JS 🪂"); - telemetry.capture({ - event: "PARAGLIDE-JS init started", - properties: { version: PARJS_PACKAGE_VERSION }, - }); const ctx = { logger, fs: nodeFs.promises, syncFs: nodeFs, root: process.cwd(), - appId: PARJS_MARKTEPLACE_ID, } as const; const ctx1 = await checkForUncommittedChanges(ctx); const ctx2 = await enforcePackageJsonExists(ctx1); const ctx3 = await initializeInlangProject(ctx2); const ctx4 = await promptForOutdir(ctx3); - telemetry.capture({ - event: "PARAGLIDE-JS init project initialized", - properties: { version: PARJS_PACKAGE_VERSION }, - }); + const ctx5 = await addParaglideJsToDevDependencies(ctx4); - telemetry.capture({ - event: "PARAGLIDE-JS init added to devDependencies", - properties: { version: PARJS_PACKAGE_VERSION }, - }); + const ctx6 = await addCompileStepToPackageJSON(ctx5); - telemetry.capture({ - event: "PARAGLIDE-JS init added compile commands", - properties: { version: PARJS_PACKAGE_VERSION }, - }); + const ctx7 = await maybeChangeTsConfig(ctx6); const ctx8 = await maybeAddSherlock(ctx7); @@ -65,11 +51,6 @@ export const initCommand = new Command() ); } - telemetry.capture({ - event: "PARAGLIDE-JS init finished", - properties: { version: PARJS_PACKAGE_VERSION }, - }); - const absoluteSettingsPath = nodePath.resolve( ctx8.projectPath, "settings.json" @@ -105,7 +86,7 @@ export const addParaglideJsToDevDependencies: CliStep< const ctx1 = await updatePackageJson({ devDependencies: async (devDeps) => ({ ...devDeps, - "@inlang/paraglide-js": PARJS_PACKAGE_VERSION, + "@inlang/paraglide-js": ENV_VARIABLES.PARJS_PACKAGE_VERSION, }), })(ctx); ctx.logger.success( diff --git a/packages/inlang-paraglide-js/src/cli/index.ts b/packages/inlang-paraglide-js/src/cli/index.ts index f061785f35..1cd745d564 100644 --- a/packages/inlang-paraglide-js/src/cli/index.ts +++ b/packages/inlang-paraglide-js/src/cli/index.ts @@ -1,5 +1,6 @@ import { Command } from "commander"; import { compileCommand } from "./commands/compile/command.js"; +import { ENV_VARIABLES } from "~/services/env-variables/index.js"; export { checkForUncommittedChanges } from "./steps/check-for-uncomitted-changes.js"; export { initializeInlangProject } from "./steps/initialize-inlang-project.js"; @@ -13,7 +14,7 @@ export const cli = new Command() .name("paraglide-js") .addCommand(compileCommand) .showHelpAfterError() - .version(PARJS_PACKAGE_VERSION); + .version(ENV_VARIABLES.PARJS_PACKAGE_VERSION); export * as Utils from "./utils.js"; export * as Defaults from "./defaults.js"; diff --git a/packages/inlang-paraglide-js/src/cli/steps/initialize-inlang-project.ts b/packages/inlang-paraglide-js/src/cli/steps/initialize-inlang-project.ts index 1232034c55..ae4a6f4701 100644 --- a/packages/inlang-paraglide-js/src/cli/steps/initialize-inlang-project.ts +++ b/packages/inlang-paraglide-js/src/cli/steps/initialize-inlang-project.ts @@ -7,6 +7,7 @@ import nodePath from "node:path"; import consola from "consola"; import fg from "fast-glob"; import fs from "node:fs"; +import { ENV_VARIABLES } from "~/services/env-variables/index.js"; export const initializeInlangProject: CliStep< { @@ -14,7 +15,6 @@ export const initializeInlangProject: CliStep< syncFs: typeof fs; logger: Logger; root: string; - appId: string; }, { project: InlangProject; @@ -30,7 +30,6 @@ export const initializeInlangProject: CliStep< fs: ctx.fs, syncFs: fs, logger: ctx.logger, - appId: ctx.appId, }); return { @@ -54,7 +53,6 @@ export const existingProjectFlow = async (ctx: { fs: typeof fs.promises; syncFs: typeof fs; logger: Logger; - appId: string; }): Promise<{ project: InlangProject; projectPath: string }> => { const NEW_PROJECT_VALUE = "newProject"; @@ -84,7 +82,7 @@ export const existingProjectFlow = async (ctx: { const project = await loadProjectFromDirectory({ path: projectPath, fs: ctx.syncFs, - // appId: ctx.appId, + appId: ENV_VARIABLES.PARJS_APP_ID, }); if ((await project.errors.get()).length > 0) { @@ -165,7 +163,6 @@ export const createNewProjectFlow = async (ctx: { fs: typeof fs.promises; syncFs: typeof fs; logger: Logger; - appId: string; }): Promise<{ project: InlangProject; /** An absolute path to the created project */ @@ -223,7 +220,7 @@ export const createNewProjectFlow = async (ctx: { const project = await loadProjectFromDirectory({ path: projectPath, fs: ctx.syncFs, - appId: ctx.appId, + appId: ENV_VARIABLES.PARJS_APP_ID, }); if ((await project.errors.get()).length > 0) { diff --git a/packages/inlang-paraglide-js/src/services/env-variables/.gitignore b/packages/inlang-paraglide-js/src/services/env-variables/.gitignore new file mode 100644 index 0000000000..c0d68902e4 --- /dev/null +++ b/packages/inlang-paraglide-js/src/services/env-variables/.gitignore @@ -0,0 +1,2 @@ +index.ts +index.js \ No newline at end of file diff --git a/packages/inlang-paraglide-js/src/services/env-variables/createIndexFile.js b/packages/inlang-paraglide-js/src/services/env-variables/createIndexFile.js new file mode 100644 index 0000000000..d5b540efcc --- /dev/null +++ b/packages/inlang-paraglide-js/src/services/env-variables/createIndexFile.js @@ -0,0 +1,36 @@ +/* eslint-disable no-undef */ +/** + * This script writes public environment variables + * to an importable env file. + * + * - The SDK must bundle this file with the rest of the SDK + * - This scripts avoids the need for a bundler + * - Must be ran before building the SDK + */ + +import fs from "node:fs/promises"; +import url from "node:url"; +import path from "node:path"; + +const dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +const packageJson = JSON.parse( + await fs.readFile(path.resolve(dirname, "../../../package.json"), "utf-8") +); + +await fs.writeFile( + dirname + "/index.ts", + ` +export const ENV_VARIABLES = { + PARJS_APP_ID: "library.inlang.paraglideJs", + PARJS_POSTHOG_TOKEN: ${ifDefined(process.env.PUBLIC_POSTHOG_TOKEN)}, + PARJS_PACKAGE_VERSION: ${ifDefined(packageJson.version)}, +} +` +); + +// console.log("✅ Created env variable index file."); + +function ifDefined(value) { + return value ? `"${value}"` : undefined; +} diff --git a/packages/inlang-paraglide-js/src/services/env-variables/index.d.ts b/packages/inlang-paraglide-js/src/services/env-variables/index.d.ts new file mode 100644 index 0000000000..47630e5062 --- /dev/null +++ b/packages/inlang-paraglide-js/src/services/env-variables/index.d.ts @@ -0,0 +1,22 @@ +/** + * Avoiding TypeScript errors before the `createIndexFile` script + * is invoked by defining the type ahead of time. + */ + +/** + * Env variables that are available at runtime. + */ +export declare const ENV_VARIABLES: { + /** + * The inlang app id. + */ + PARJS_APP_ID: string; + PARJS_POSTHOG_TOKEN?: string; + /** + * The Package version from package.json + * + * This value is inlined by vite during build to avoid having to read the package.json + * at runtime + */ + PARJS_PACKAGE_VERSION: string; +}; diff --git a/packages/inlang-paraglide-js/src/services/telemetry/capture.test.ts b/packages/inlang-paraglide-js/src/services/telemetry/capture.test.ts new file mode 100644 index 0000000000..671d4c8cbd --- /dev/null +++ b/packages/inlang-paraglide-js/src/services/telemetry/capture.test.ts @@ -0,0 +1,49 @@ +import { expect, test, vi } from "vitest"; +import { capture } from "./capture.js"; +import type { ENV_VARIABLES } from "../env-variables/index.js"; + +test("it should not capture if telemetry is off", async () => { + // @ts-expect-error - global.fetch is not defined + global.fetch = vi.fn(() => Promise.resolve()); + + vi.mock("../env-variables/index.js", async () => { + return { + ENV_VARIABLES: { + PARJS_POSTHOG_TOKEN: "mock-defined", + } satisfies Partial, + }; + }); + + await capture("PARAGLIDE-JS compile executed", { + projectId: "test", + settings: { + telemetry: "off", + }, + properties: {}, + }); + + expect(global.fetch).not.toHaveBeenCalled(); +}); + +test("it should not capture if telemetry is NOT off", async () => { + // @ts-expect-error - global.fetch is not defined + global.fetch = vi.fn(() => Promise.resolve()); + + vi.mock("../env-variables/index.js", async () => { + return { + ENV_VARIABLES: { + PARJS_POSTHOG_TOKEN: "mock-defined", + } satisfies Partial, + }; + }); + + await capture("PARAGLIDE-JS compile executed", { + projectId: "test", + settings: { + telemetry: undefined, + }, + properties: {}, + }); + + expect(global.fetch).toHaveBeenCalled(); +}); diff --git a/packages/inlang-paraglide-js/src/services/telemetry/capture.ts b/packages/inlang-paraglide-js/src/services/telemetry/capture.ts new file mode 100644 index 0000000000..95aa5ffa0a --- /dev/null +++ b/packages/inlang-paraglide-js/src/services/telemetry/capture.ts @@ -0,0 +1,88 @@ +import type { ProjectSettings } from "@inlang/sdk"; +import { ENV_VARIABLES } from "../env-variables/index.js"; +import type { TelemetryEvent } from "./events.js"; + +/** + * Capture an event. + * + * - manually calling the PostHog API because the SDKs were not platform angostic (and generally bloated) + */ +export const capture = async ( + event: TelemetryEvent, + args: { + projectId: string; + /** + * Please use snake_case for property names. + */ + properties: Record; + settings: Pick; + } +) => { + if (args.settings.telemetry === "off") { + return; + } else if (ENV_VARIABLES.PARJS_POSTHOG_TOKEN === undefined) { + return; + } + try { + await fetch("https://eu.posthog.com/capture/", { + method: "POST", + body: JSON.stringify({ + api_key: ENV_VARIABLES.PARJS_POSTHOG_TOKEN, + event, + // id is "unknown" because no user information is available + distinct_id: "unknown", + properties: { + $groups: { project: args.projectId }, + ...args.properties, + }, + }), + }); + await identifyProject({ + projectId: args.projectId, + // using the id for now as a name but can be changed in the future + // we need at least one property to make a project visible in the dashboar + properties: { name: args.projectId }, + }); + } catch { + // captureError(e); + } +}; + +/** + * Identifying a project is needed. + * + * Otherwise, the project will not be visible in the PostHog dashboard. + */ +const identifyProject = async (args: { + projectId: string; + /** + * Please use snake_case for property names. + */ + properties: Record; +}) => { + // do not send events if the token is not set + // (assuming this eases testing) + if (ENV_VARIABLES.PARJS_POSTHOG_TOKEN === undefined) { + return; + } + try { + await fetch("https://eu.posthog.com/capture/", { + method: "POST", + body: JSON.stringify({ + api_key: ENV_VARIABLES.PARJS_POSTHOG_TOKEN, + event: "$groupidentify", + // id is "unknown" because no user information is available + distinct_id: "unknown", + properties: { + $group_type: "project", + $group_key: args.projectId, + $group_set: { + ...args.properties, + }, + }, + }), + }); + } catch { + // + } +}; diff --git a/packages/inlang-paraglide-js/src/services/telemetry/events.ts b/packages/inlang-paraglide-js/src/services/telemetry/events.ts index 3177e0b6a2..c92d7aad7b 100644 --- a/packages/inlang-paraglide-js/src/services/telemetry/events.ts +++ b/packages/inlang-paraglide-js/src/services/telemetry/events.ts @@ -3,7 +3,7 @@ * * Exists to avoid typos/always set the correct event name and properties. */ -export type TelemetryEvents = +export type TelemetryEvent = | "PARAGLIDE-JS compile executed" | "PARAGLIDE-JS init started" | "PARAGLIDE-JS init project initialized" diff --git a/packages/inlang-paraglide-js/src/services/telemetry/implementation.ts b/packages/inlang-paraglide-js/src/services/telemetry/implementation.ts deleted file mode 100644 index a5ca7b8973..0000000000 --- a/packages/inlang-paraglide-js/src/services/telemetry/implementation.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { PostHog } from "posthog-node"; -import type { TelemetryEvents } from "./events.js"; - -const posthogToken = PARJS_POSTHOG_TOKEN; - -const posthog = new PostHog(posthogToken, { - host: "https://eu.posthog.com", - // Events are not captured if not immediately flushed. - // - // Posthog shouldn't batch events because CLI commands - // are short-lived, see https://posthog.com/docs/libraries/node. - flushAt: 1, - flushInterval: 0, - requestTimeout: 1000, -}); - -/** - * Telmetry for the CLI. - * - * Auto injects the git origin url. - */ -export const telemetry = new Proxy(posthog, { - get(target, prop: keyof PostHog) { - if (prop === "capture") return capture; - return target[prop]; - }, -}) as unknown as Omit & { capture: typeof capture }; - -/** - * Wrapper to auto inject the git origin url and user id. - */ -function capture(args: CaptureEventArguments, projectId?: string) { - if (!posthogToken) return; //if there is no posthog token defined, that's ok - - const data: Parameters[0] = { - ...args, - distinctId: "unknown", - groups: projectId ? { project: projectId } : {}, - }; - - return posthog.capture(data); -} - -/** - * Typesafe wrapper around the `telemetryNode.capture` method. - * - * Exists to avoid typos/always set the correct event name and properties. - */ -type CaptureEventArguments = Omit< - Parameters[0], - "distinctId" | "groups" -> & { - event: TelemetryEvents; -}; diff --git a/packages/inlang-paraglide-js/src/services/telemetry/index.ts b/packages/inlang-paraglide-js/src/services/telemetry/index.ts deleted file mode 100644 index 924dcd3e62..0000000000 --- a/packages/inlang-paraglide-js/src/services/telemetry/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { telemetry } from "./implementation.js"; -// export { gitOrigin } from "./implementation.js" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dccc8cccce..3992b31df8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,9 +135,6 @@ importers: json5: specifier: 2.2.3 version: 2.2.3 - posthog-node: - specifier: ^4.0.1 - version: 4.3.2 prettier: specifier: ^3.4.2 version: 3.4.2 @@ -6559,10 +6556,6 @@ packages: posthog-js@1.195.0: resolution: {integrity: sha512-ja+d/uogH9IPPnnM512uL5a9Igzz4K1OvBJNybCSbt6hqjSC+c5XaY3XH8t4D3RFz7aU5Di3trsrh/YNkSbF6A==} - posthog-node@4.3.2: - resolution: {integrity: sha512-vy8Mt9IEfniUgqQ1rOCQ31CBO1VNqDGd3ZtHlWR9/YfU6RiuK+9pUXPb4h6HTGzQmjL8NFnjd8K8NMXSX8S6MQ==} - engines: {node: '>=15.0.0'} - preact@10.25.1: resolution: {integrity: sha512-frxeZV2vhQSohQwJ7FvlqC40ze89+8friponWUFeVEkaCfhC6Eu4V0iND5C9CXz8JLndV07QRDeXzH1+Anz5Og==} @@ -6987,9 +6980,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rusha@0.8.14: - resolution: {integrity: sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==} - rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} @@ -15252,13 +15242,6 @@ snapshots: preact: 10.25.1 web-vitals: 4.2.4 - posthog-node@4.3.2: - dependencies: - axios: 1.7.7 - rusha: 0.8.14 - transitivePeerDependencies: - - debug - preact@10.25.1: {} prelude-ls@1.2.1: {} @@ -15726,8 +15709,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - rusha@0.8.14: {} - rxjs@7.8.1: dependencies: tslib: 2.8.0