From e1a61b2101a79cdeaab57e0233fb9af369c5a20d Mon Sep 17 00:00:00 2001 From: psugihara Date: Mon, 4 Dec 2023 12:04:34 -0800 Subject: [PATCH 1/7] remove anonymous telemetry stuff --- .env.example | 3 - packages/cli/package.json | 1 - .../commands/__test__/exportPreviews.test.ts | 1 - packages/cli/src/commands/exportPreviews.ts | 1 - packages/cli/src/commands/init.ts | 2 - packages/cli/src/commands/preview/preview.ts | 1 - .../cli/src/commands/preview/server/start.ts | 2 - packages/cli/src/commands/server.ts | 1 - packages/cli/src/components/PosthogScript.tsx | 30 -- packages/cli/src/custom.d.ts | 1 - packages/cli/src/moduleManifest.ts | 2 +- packages/cli/src/pages/_app.tsx | 2 - .../src/util/__test__/buildHandler.test.ts | 79 +---- packages/cli/src/util/buildHandler.ts | 60 +--- .../__snapshots__/config.test.ts.snap | 10 +- .../util/config/__test__/anonymousId.test.ts | 23 -- .../src/util/config/__test__/config.test.ts | 82 +---- packages/cli/src/util/config/anonymousId.ts | 22 -- packages/cli/src/util/config/index.ts | 78 ++--- .../src/util/postHog/__test__/index.test.ts | 137 -------- .../__test__/indexMockMailingVersion.test.ts | 44 --- packages/cli/src/util/postHog/client.ts | 27 -- packages/cli/src/util/postHog/index.ts | 67 ---- .../cli/src/util/postHog/posthogApiKey.ts | 1 - packages/core/package.json | 2 +- packages/core/src/__test__/index.test.tsx | 300 ------------------ packages/core/src/index.ts | 19 -- .../indexMockMailingCoreVersion.test.ts | 44 --- packages/core/src/util/postHog/client.ts | 29 -- packages/core/src/util/postHog/index.ts | 57 ---- scripts/release | 12 - yarn.lock | 20 +- 32 files changed, 48 insertions(+), 1112 deletions(-) delete mode 100644 packages/cli/src/components/PosthogScript.tsx delete mode 100644 packages/cli/src/util/config/__test__/anonymousId.test.ts delete mode 100644 packages/cli/src/util/config/anonymousId.ts delete mode 100644 packages/cli/src/util/postHog/__test__/index.test.ts delete mode 100644 packages/cli/src/util/postHog/__test__/indexMockMailingVersion.test.ts delete mode 100644 packages/cli/src/util/postHog/client.ts delete mode 100644 packages/cli/src/util/postHog/index.ts delete mode 100644 packages/cli/src/util/postHog/posthogApiKey.ts delete mode 100644 packages/core/src/util/postHog/__test__/indexMockMailingCoreVersion.test.ts delete mode 100644 packages/core/src/util/postHog/client.ts delete mode 100644 packages/core/src/util/postHog/index.ts diff --git a/.env.example b/.env.example index 80c8236ee..ad1335fc9 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,3 @@ MAILING_SES_PASSWORD= MAILING_DATABASE_URL_TEST= WEB_DATABASE_URL_TEST= # Note: MAILING_SESSION_PASSWORD must also be set for integration tests to run - -## Anonymous telemetry -POSTHOG_API_KEY= diff --git a/packages/cli/package.json b/packages/cli/package.json index d6be6f05d..7c06cf883 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -84,7 +84,6 @@ "nodemailer": "6.8.0", "open": "^8.4.0", "postcss": "^8.4.16", - "posthog-node": "^2.2.3", "prettier": "^2.7.1", "prisma": "^4.4.0", "prompts": "^2.4.2", diff --git a/packages/cli/src/commands/__test__/exportPreviews.test.ts b/packages/cli/src/commands/__test__/exportPreviews.test.ts index 13c93e7cc..00f564e28 100644 --- a/packages/cli/src/commands/__test__/exportPreviews.test.ts +++ b/packages/cli/src/commands/__test__/exportPreviews.test.ts @@ -14,7 +14,6 @@ describe("exportPreviews command", () => { typescript: true, emailsDir: "./packages/cli/src/__mocks__/emails", outDir: "./previews_html", - anonymousId: "TEST_VALUE", })); }); diff --git a/packages/cli/src/commands/exportPreviews.ts b/packages/cli/src/commands/exportPreviews.ts index 87efa13cd..cdd1201d8 100644 --- a/packages/cli/src/commands/exportPreviews.ts +++ b/packages/cli/src/commands/exportPreviews.ts @@ -15,7 +15,6 @@ export type ExportPreviewsArgs = ArgumentsCamelCase<{ outDir?: string; quiet?: boolean; minify?: boolean; - anonymousId?: string | null; skipLint?: boolean; }>; diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 9dedd1eac..117c4bebe 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -15,7 +15,6 @@ export type InitArguments = ArgumentsCamelCase<{ typescript?: boolean; port?: number; quiet?: boolean; - anonymousId?: string | null; }>; export const command = ["$0", "init"]; @@ -106,7 +105,6 @@ export const handler = buildHandler( port: argv.port, quiet: argv.quiet, emailsDir: argv.emailsDir, - anonymousId: argv.anonymousId, $0: argv.$0, _: argv._, }; diff --git a/packages/cli/src/commands/preview/preview.ts b/packages/cli/src/commands/preview/preview.ts index edeb186d2..f3e86bf4e 100644 --- a/packages/cli/src/commands/preview/preview.ts +++ b/packages/cli/src/commands/preview/preview.ts @@ -7,7 +7,6 @@ export type PreviewArgs = ArgumentsCamelCase<{ port?: number; quiet?: boolean; emailsDir?: string; - anonymousId?: string | null; }>; export const command = "preview"; diff --git a/packages/cli/src/commands/preview/server/start.ts b/packages/cli/src/commands/preview/server/start.ts index 896c07cea..f63fb3ab7 100644 --- a/packages/cli/src/commands/preview/server/start.ts +++ b/packages/cli/src/commands/preview/server/start.ts @@ -4,7 +4,6 @@ import open from "open"; import next from "next"; import { pathExists } from "fs-extra"; import { parse } from "url"; -import { shutdown as shutdownAnalytics } from "../../../util/postHog"; import { getPreviewsDirectory } from "../../../util/paths"; import { error, log, debug } from "../../../util/serverLogger"; @@ -130,7 +129,6 @@ export default async function startPreviewServer() { .on("error", async function onServerError(e: NodeJS.ErrnoException) { if (e.code === "EADDRINUSE") { error(`port ${port} is taken, is mailing already running?`); - await shutdownAnalytics(); process.exit(1); } else { error("preview server error:", JSON.stringify(e)); diff --git a/packages/cli/src/commands/server.ts b/packages/cli/src/commands/server.ts index b7f6de309..bc438cd7f 100644 --- a/packages/cli/src/commands/server.ts +++ b/packages/cli/src/commands/server.ts @@ -13,7 +13,6 @@ export type ServerArguments = ArgumentsCamelCase<{ port?: number; quiet?: boolean; subcommand?: string; - anonymousId?: string | null; }>; export const command = ["server [subcommand]"]; diff --git a/packages/cli/src/components/PosthogScript.tsx b/packages/cli/src/components/PosthogScript.tsx deleted file mode 100644 index 6f857a4b6..000000000 --- a/packages/cli/src/components/PosthogScript.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { POSTHOG_API_KEY } from "../util/postHog/posthogApiKey"; -import { MAILING_VERSION } from "../const/mailingVersion"; -import { config } from "../feManifest"; - -export default function PosthogScript() { - if (process.env.NODE_ENV !== "production" || POSTHOG_API_KEY === undefined) { - return null; - } - - const loadPosthogScript = `!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags".split(" "),n=0;n - ); -} diff --git a/packages/cli/src/custom.d.ts b/packages/cli/src/custom.d.ts index f59d68b83..b72b3874f 100644 --- a/packages/cli/src/custom.d.ts +++ b/packages/cli/src/custom.d.ts @@ -38,7 +38,6 @@ type HtmlLintError = { type ViewMode = "desktop" | "mobile" | "html"; type MailingConfig = { - anonymousId?: string | null; emailsDir?: string; port?: number; quiet?: boolean; diff --git a/packages/cli/src/moduleManifest.ts b/packages/cli/src/moduleManifest.ts index 39e3955fe..5d3941b7d 100644 --- a/packages/cli/src/moduleManifest.ts +++ b/packages/cli/src/moduleManifest.ts @@ -14,7 +14,7 @@ const previews = { const templates = { Welcome, }; -const config = { anonymousId: null }; +const config = {}; const manifest = { sendMail, config, templates, previews }; export { sendMail, config, templates, previews }; diff --git a/packages/cli/src/pages/_app.tsx b/packages/cli/src/pages/_app.tsx index 4aee9e632..d96d78a94 100644 --- a/packages/cli/src/pages/_app.tsx +++ b/packages/cli/src/pages/_app.tsx @@ -4,7 +4,6 @@ import Head from "next/head"; import { HamburgerProvider } from "../components/HamburgerContext"; import NavBar from "../components/NavBar/NavBar"; import "../styles/globals.css"; -import PosthogScript from "../components/PosthogScript"; export default function Mailing({ Component, @@ -13,7 +12,6 @@ export default function Mailing({ return ( - Mailing {pageProps.user ? ( diff --git a/packages/cli/src/util/__test__/buildHandler.test.ts b/packages/cli/src/util/__test__/buildHandler.test.ts index ed7426088..1fa9ddc70 100644 --- a/packages/cli/src/util/__test__/buildHandler.test.ts +++ b/packages/cli/src/util/__test__/buildHandler.test.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import { buildHandler } from "../buildHandler"; import { log } from "../serverLogger"; -import * as postHog from "../postHog"; import fsExtra from "fs-extra"; import * as config from "../config"; @@ -17,7 +16,6 @@ describe("buildHandler", () => { it("returns early if ./package.json is not found", async () => { const handler = buildHandler(async () => {}, {}); const argv = { - anonymousId: "abc", emailsDir: "./emails", }; @@ -40,9 +38,7 @@ describe("buildHandler", () => { it("throws an error if emailsDir is not specified in argv", async () => { const handler = buildHandler(async () => {}, {}); - const argv = { - anonymousId: "abc", - }; + const argv = {}; await expect(async () => { await handler(argv); @@ -52,7 +48,6 @@ describe("buildHandler", () => { it("calls setConfig and writeDefaultConfigFile", async () => { const handler = buildHandler(async () => {}, {}); const argv = { - anonymousId: "abc", emailsDir: "./emails", port: 3883, quiet: true, @@ -73,76 +68,4 @@ describe("buildHandler", () => { expect(mockWriteDefaultConfigFile).toHaveBeenCalled(); }); }); - - describe("calling postHog commands", () => { - it("without properties", async () => { - const mockCapture = jest - .spyOn(postHog, "capture") - .mockImplementation(() => {}); - - const mockShutdown = jest - .spyOn(postHog, "shutdown") - .mockImplementation(async () => {}); - - const handler = buildHandler(async () => {}, { - captureOptions: () => { - return { event: "handler invoked" }; - }, - }); - - const argv = { - anonymousId: "abc", - emailsDir: "./emails", - }; - - await handler(argv); - - expect(mockCapture).toHaveBeenCalledWith({ - distinctId: "abc", - event: "handler invoked", - properties: { - nodeEnv: "test", - }, - }); - - expect(mockShutdown).toHaveBeenCalledTimes(1); - }); - - it("with properties", async () => { - const mockCapture = jest - .spyOn(postHog, "capture") - .mockImplementation(() => {}); - - const mockShutdown = jest - .spyOn(postHog, "shutdown") - .mockImplementation(async () => {}); - - const handler = buildHandler(async () => {}, { - captureOptions: () => { - return { - event: "handler invoked", - properties: { subcommand: "start" }, - }; - }, - }); - - const argv = { - anonymousId: "abc", - emailsDir: "./emails", - }; - - await handler(argv); - - expect(mockCapture).toHaveBeenCalledWith({ - distinctId: "abc", - event: "handler invoked", - properties: { - nodeEnv: "test", - subcommand: "start", - }, - }); - - expect(mockShutdown).toHaveBeenCalledTimes(1); - }); - }); }); diff --git a/packages/cli/src/util/buildHandler.ts b/packages/cli/src/util/buildHandler.ts index d5554af6e..15b307659 100644 --- a/packages/cli/src/util/buildHandler.ts +++ b/packages/cli/src/util/buildHandler.ts @@ -1,57 +1,25 @@ -import { capture, shutdown as shutdownAnalytics } from "./postHog"; import { log } from "./serverLogger"; import { existsSync } from "fs-extra"; import { setConfig, writeDefaultConfigFile } from "./config"; -type CaptureProperties = { - subcommand?: string; - nodeEnv?: string; -}; - -type BuildHandlerOptions = { - captureOptions?: (argv: any) => { - event: string; - properties?: CaptureProperties; - }; -}; - -export function buildHandler( - handler: (argv: any) => Promise, - options: BuildHandlerOptions -) { +export function buildHandler(handler: (argv: any) => Promise) { return async (argv: any) => { - try { - if (!existsSync("./package.json")) { - log("No package.json found. Please run from the project root."); - return; - } - - // check for presence of options that apply to every command - if (!argv.emailsDir) throw new Error("emailsDir option is not set"); - - setConfig({ - emailsDir: argv.emailsDir, - port: argv.port, - quiet: argv.quiet, - }); - - writeDefaultConfigFile(); + if (!existsSync("./package.json")) { + log("No package.json found. Please run from the project root."); + return; + } - if (options.captureOptions) { - const captureOpts = { - distinctId: argv.anonymousId, - ...options.captureOptions(argv), - }; + // check for presence of options that apply to every command + if (!argv.emailsDir) throw new Error("emailsDir option is not set"); - if (!captureOpts.properties) captureOpts.properties = {}; - captureOpts.properties.nodeEnv = process.env.NODE_ENV; + setConfig({ + emailsDir: argv.emailsDir, + port: argv.port, + quiet: argv.quiet, + }); - capture(captureOpts); - } + writeDefaultConfigFile(); - await handler(argv); - } finally { - await shutdownAnalytics(); - } + await handler(argv); }; } diff --git a/packages/cli/src/util/config/__test__/__snapshots__/config.test.ts.snap b/packages/cli/src/util/config/__test__/__snapshots__/config.test.ts.snap index c0b670e00..2155bf81d 100644 --- a/packages/cli/src/util/config/__test__/__snapshots__/config.test.ts.snap +++ b/packages/cli/src/util/config/__test__/__snapshots__/config.test.ts.snap @@ -8,24 +8,16 @@ exports[`writeDefaultConfigFile writes mailing.config.json if it doesn't exist 1 { "typescript": true, "emailsDir": "./emails", - "outDir": "./previews_html", - "anonymousId": "TEST_VALUE" + "outDir": "./previews_html" } ", ], - [ - "mailing collects anonymous telemetry data about usage", - ], ], "results": [ { "type": "return", "value": undefined, }, - { - "type": "return", - "value": undefined, - }, ], } `; diff --git a/packages/cli/src/util/config/__test__/anonymousId.test.ts b/packages/cli/src/util/config/__test__/anonymousId.test.ts deleted file mode 100644 index 8b8c1f8d8..000000000 --- a/packages/cli/src/util/config/__test__/anonymousId.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - getOrSetGeneratedAnonymousId, - getGeneratedAnonymousId, -} from "../anonymousId"; - -describe("anonymousId", () => { - it("read and write anonymouse id's", () => { - // before getOrSetGeneratedAnonymousId has been called, getGeneratedAnonymousId should return undefined - expect(getGeneratedAnonymousId()).toBe(undefined); - - // getOrSetGeneratedAnonymousId should generate an anonymousId and return it - const anonymousId = getOrSetGeneratedAnonymousId(); - expect(typeof anonymousId).toBe("string"); - - // it should look like a UUID - expect(anonymousId).toMatch(/[a-z0-9-]+/); - expect(anonymousId.split(/-/).length).toBe(5); - - // getGeneratedAnonymousId and getOrSetGeneratedAnonymousId should both return the same anonymousId as before - expect(getOrSetGeneratedAnonymousId()).toBe(anonymousId); - expect(getGeneratedAnonymousId()).toBe(anonymousId); - }); -}); diff --git a/packages/cli/src/util/config/__test__/config.test.ts b/packages/cli/src/util/config/__test__/config.test.ts index 41776b5b8..2bf2bc0f5 100644 --- a/packages/cli/src/util/config/__test__/config.test.ts +++ b/packages/cli/src/util/config/__test__/config.test.ts @@ -1,6 +1,5 @@ import { log } from "../../serverLogger"; import fsExtra from "fs-extra"; -import crypto from "crypto"; import { defaults, looksLikeTypescriptProject, @@ -36,8 +35,7 @@ describe("writeDefaultConfigFile", () => { const defaultJsonString = `{ "typescript": true, "emailsDir": "./emails", - "outDir": "./previews_html", - "anonymousId": "TEST_VALUE" + "outDir": "./previews_html" } `; @@ -49,13 +47,8 @@ describe("writeDefaultConfigFile", () => { .spyOn(fsExtra, "existsSync") .mockImplementation(() => false); - const mockUUID = jest - .spyOn(crypto, "randomUUID") - .mockImplementation(() => "TEST_VALUE"); - writeDefaultConfigFile(); - expect(mockUUID).toHaveBeenCalled(); expect(mockExistsSync).toHaveBeenCalled(); expect(mockWriteFileSync).toHaveBeenCalledWith( "./mailing.config.json", @@ -64,7 +57,7 @@ describe("writeDefaultConfigFile", () => { expect(log).toMatchSnapshot(); }); - it("does not write mailing.config.json if it exists and has anonymousId", () => { + it("does not write mailing.config.json if it exists", () => { // config file exists jest.spyOn(fsExtra, "existsSync").mockImplementation((_path) => true); @@ -74,7 +67,6 @@ describe("writeDefaultConfigFile", () => { typescript: true, emailsDir: "./emails", outDir: "./previews_html", - anonymousId: "TEST_VALUE", })); const mockWriteFileSync = jest @@ -82,75 +74,7 @@ describe("writeDefaultConfigFile", () => { .mockImplementation(() => false); writeDefaultConfigFile(); - expect(mockReadJSON).toHaveBeenCalled(); - expect(mockWriteFileSync).not.toHaveBeenCalled(); - expect(log).not.toHaveBeenCalled(); - }); - - it("adds an anonymousId to mailing.config.json if if it exists and has no anonymousId", () => { - // config file exists - const mockExistsSync = jest - .spyOn(fsExtra, "existsSync") - .mockImplementation((_path) => true); - - const mockWriteFileSync = jest - .spyOn(fsExtra, "writeFileSync") - .mockImplementation(() => false); - - const mockReadJSON = jest - .spyOn(fsExtra, "readJSONSync") - .mockImplementation(() => ({ - typescript: true, - emailsDir: "./emails", - outDir: "./previews_html", - })); - - writeDefaultConfigFile(); - expect(mockExistsSync).toHaveBeenCalled(); - expect(mockReadJSON).toHaveBeenCalled(); - expect(mockWriteFileSync).toHaveBeenCalled(); - expect(log).toHaveBeenCalled(); - }); - - it("does not add an anonymousId to mailing.config.json if if it exists and anonymousId is set to null or blank string", () => { - // config file exists - const mockExistsSync = jest - .spyOn(fsExtra, "existsSync") - .mockImplementation((_path) => true); - - const mockWriteFileSync = jest - .spyOn(fsExtra, "writeFileSync") - .mockImplementation(() => false); - - // anonymousId is null - const mockReadJSONWithNull = jest - .spyOn(fsExtra, "readJSONSync") - .mockImplementation(() => ({ - typescript: true, - emailsDir: "./emails", - outDir: "./previews_html", - anonymousId: null, - })); - - writeDefaultConfigFile(); - expect(mockExistsSync).toHaveBeenCalled(); - expect(mockReadJSONWithNull).toHaveBeenCalled(); - expect(mockWriteFileSync).not.toHaveBeenCalled(); - expect(log).not.toHaveBeenCalled(); - - // anonymousId is "" - const mockReadJSONWithBlankString = jest - .spyOn(fsExtra, "readJSONSync") - .mockImplementation(() => ({ - typescript: true, - emailsDir: "./emails", - outDir: "./previews_html", - anonymousId: "", - })); - - writeDefaultConfigFile(); - expect(mockExistsSync).toHaveBeenCalled(); - expect(mockReadJSONWithBlankString).toHaveBeenCalled(); + expect(mockReadJSON).not.toHaveBeenCalled(); expect(mockWriteFileSync).not.toHaveBeenCalled(); expect(log).not.toHaveBeenCalled(); }); diff --git a/packages/cli/src/util/config/anonymousId.ts b/packages/cli/src/util/config/anonymousId.ts deleted file mode 100644 index 7c212bd8b..000000000 --- a/packages/cli/src/util/config/anonymousId.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { randomUUID } from "crypto"; - -/* - Functions for generating an anonymousId and get/set to a singleton - this is necessary to report analytics the first time you run init, - when you had no config and so argv has no anonymousId set -*/ -let generatedAnonymousId: string | undefined; -// only call getOrSetGeneratedAnonymousId when an anonymousId is missing and *should be set* -// because otherwise setting generatedAnonymousId will create side effects. The only time we do this -// is when we're generating an anonymousId for the first time -export function getOrSetGeneratedAnonymousId() { - if (generatedAnonymousId) return generatedAnonymousId; - - const id = randomUUID(); - generatedAnonymousId = id; - return id; -} - -export function getGeneratedAnonymousId() { - return generatedAnonymousId; -} diff --git a/packages/cli/src/util/config/index.ts b/packages/cli/src/util/config/index.ts index f99f0e6e0..f4b9902b5 100644 --- a/packages/cli/src/util/config/index.ts +++ b/packages/cli/src/util/config/index.ts @@ -1,12 +1,8 @@ import { existsSync, writeFileSync } from "fs-extra"; -import { readJSONverbose, readPackageJSON } from "../paths"; -import { log, debug, logPlain } from "../serverLogger"; +import { readPackageJSON } from "../paths"; +import { log, logPlain } from "../serverLogger"; import { pick } from "lodash"; import * as prettier from "prettier"; -import { - getOrSetGeneratedAnonymousId, - getGeneratedAnonymousId, -} from "./anonymousId"; export const MAILING_CONFIG_FILE = "./mailing.config.json"; @@ -36,23 +32,11 @@ export function defaults() { } // options to include in the default config file -const DEFAULT_KEYS_FOR_CONFIG_FILE = [ - "typescript", - "emailsDir", - "outDir", - "anonymousId", -]; +const DEFAULT_KEYS_FOR_CONFIG_FILE = ["typescript", "emailsDir", "outDir"]; // an object to JSON stringify and write to the default config file function defaultsForConfigFile() { - // set anonymousId here and not in DEFAULTS so that it getOrSetGeneratedAnonymousId is only called - // when an anonymousId needs to be generated - - const defaultsToWriteToConfig = { - ...defaults(), - anonymousId: getOrSetGeneratedAnonymousId(), - }; - + const defaultsToWriteToConfig = defaults(); return pick(defaultsToWriteToConfig, DEFAULT_KEYS_FOR_CONFIG_FILE); } @@ -67,44 +51,20 @@ export function looksLikeTypescriptProject(): boolean { // write the default mailing.config.json file to get you started export function writeDefaultConfigFile(): void { - if (existsSync(MAILING_CONFIG_FILE)) { - // read the JSON file - const json = readJSONverbose(MAILING_CONFIG_FILE); - - // check if anonymousId in JSON object - if ("anonymousId" in json) return; - - // if not, add it - json.anonymousId = getOrSetGeneratedAnonymousId(); + if (existsSync(MAILING_CONFIG_FILE)) return; - // ... and overwrite the JSON file - debug( - `patching mailing.config.json to include anonymousId ${getGeneratedAnonymousId()}` - ); - const configJsonString = prettier.format(JSON.stringify(json), { + const configJsonString = prettier.format( + JSON.stringify(defaultsForConfigFile()), + { parser: "json", printWidth: 0, - }); - - writeFileSync(MAILING_CONFIG_FILE, configJsonString); - - log( - `updated mailing.config.json in your project with the following contents: - ${configJsonString}` - ); - } else { - const configJsonString = prettier.format( - JSON.stringify(defaultsForConfigFile()), - { - parser: "json", - printWidth: 0, - } - ); - - writeFileSync(MAILING_CONFIG_FILE, configJsonString); - - // ascii art moment - logPlain(` + } + ); + + writeFileSync(MAILING_CONFIG_FILE, configJsonString); + + // ascii art moment + logPlain(` ,, ,, ,, \`7MMM. ,MMF' db \`7MM db MMMb dPMM MM @@ -117,12 +77,10 @@ export function writeDefaultConfigFile(): void { Ybmmmd' `); - log( - `added mailing.config.json in your project with the following contents: + log( + `added mailing.config.json in your project with the following contents: ${configJsonString}` - ); - } - log("mailing collects anonymous telemetry data about usage"); + ); } /* Preview server config singleton */ diff --git a/packages/cli/src/util/postHog/__test__/index.test.ts b/packages/cli/src/util/postHog/__test__/index.test.ts deleted file mode 100644 index 322a75320..000000000 --- a/packages/cli/src/util/postHog/__test__/index.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { capture, shutdown } from ".."; -import * as postHogClient from "../client"; -import * as moduleManifestUtil from "../../moduleManifestUtil"; -import * as anonymousId from "../../config/anonymousId"; -import { PostHog } from "posthog-node"; - -describe("postHog", () => { - const MM_DEV_OG = process.env.MM_DEV; - const MM_DEV_E2E_OG = process.env.MM_E2E; - - let mockPostHogClient: PostHog; - let mockPostHogClientWithShutdownError: PostHog; - - beforeEach(() => { - mockPostHogClient = { capture: jest.fn() } as unknown as PostHog; - mockPostHogClientWithShutdownError = { - shutdownAsync: jest.fn().mockRejectedValue(new Error("timeout")), - } as unknown as PostHog; - jest.restoreAllMocks(); - delete process.env.MM_DEV; - delete process.env.MM_E2E; - }); - - afterEach(() => { - process.env.MM_DEV = MM_DEV_OG; - process.env.MM_E2E = MM_DEV_E2E_OG; - }); - - it("should not raise an error if posthog shutdown raises an error (e.g. a timeout)", async () => { - jest - .spyOn(postHogClient, "getPostHogClient") - .mockImplementation(() => mockPostHogClientWithShutdownError); - - expect(async () => { - await shutdown(); - }).not.toThrow(); - }); - - it("should call capture on the postHog client", () => { - jest - .spyOn(postHogClient, "postHogClient") - .mockImplementation(() => mockPostHogClient); - - capture({ - distinctId: "abc", - event: "ate pizza", - }); - - expect(mockPostHogClient.capture).toHaveBeenCalledWith({ - distinctId: "abc", - event: "ate pizza", - properties: {}, - }); - }); - - it("should call capture on the postHog client - with config.anonymousId if options.distinctId is blank", () => { - jest.spyOn(moduleManifestUtil, "getConfig").mockImplementation(() => { - return { - anonymousId: "config-xyz", - }; - }); - - jest - .spyOn(postHogClient, "postHogClient") - .mockImplementation(() => mockPostHogClient); - - capture({ - event: "ate pizza", - }); - - expect(mockPostHogClient.capture).toHaveBeenCalledWith({ - distinctId: "config-xyz", - event: "ate pizza", - properties: {}, - }); - }); - - it("should call capture on the postHog client - with generatedAnonymousId if options.distinctId and config.anonymousId are blank", () => { - jest - .spyOn(anonymousId, "getGeneratedAnonymousId") - .mockImplementation(() => "generated-xyz"); - - jest - .spyOn(postHogClient, "postHogClient") - .mockImplementation(() => mockPostHogClient); - - capture({ - event: "ate pizza", - }); - - expect(mockPostHogClient.capture).toHaveBeenCalledWith({ - distinctId: "generated-xyz", - event: "ate pizza", - properties: {}, - }); - }); - - it("should not call capture if there is no anonymousId", () => { - jest - .spyOn(postHogClient, "postHogClient") - .mockImplementation(() => mockPostHogClient); - - capture({ - event: "ate pizza", - }); - - expect(mockPostHogClient.capture).not.toHaveBeenCalled(); - }); - - it("should not call capture if MM_DEV", () => { - process.env.MM_DEV = "1"; - - jest - .spyOn(postHogClient, "postHogClient") - .mockImplementation(() => mockPostHogClient); - - capture({ - event: "ate pizza", - }); - - expect(mockPostHogClient.capture).not.toHaveBeenCalled(); - }); - - it("should not call capture if MM_E2E", () => { - process.env.MM_E2E = "1"; - - jest - .spyOn(postHogClient, "postHogClient") - .mockImplementation(() => mockPostHogClient); - - capture({ - event: "ate pizza", - }); - - expect(mockPostHogClient.capture).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/cli/src/util/postHog/__test__/indexMockMailingVersion.test.ts b/packages/cli/src/util/postHog/__test__/indexMockMailingVersion.test.ts deleted file mode 100644 index b3de973f9..000000000 --- a/packages/cli/src/util/postHog/__test__/indexMockMailingVersion.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { capture } from ".."; -import * as postHogClient from "../client"; -import { PostHog } from "posthog-node"; - -jest.mock("../../../const/mailingVersion", () => ({ - MAILING_VERSION: "mock-mailing-version", -})); - -describe("posthog", () => { - const MM_DEV_OG = process.env.MM_DEV; - const MM_DEV_E2E_OG = process.env.MM_E2E; - - let mockPostHogClient: PostHog; - beforeEach(() => { - mockPostHogClient = { capture: jest.fn() } as unknown as PostHog; - jest.restoreAllMocks(); - delete process.env.MM_DEV; - delete process.env.MM_E2E; - }); - - afterEach(() => { - process.env.MM_DEV = MM_DEV_OG; - process.env.MM_E2E = MM_DEV_E2E_OG; - }); - - it("should include MAILING_VERSION because it is defined", () => { - jest - .spyOn(postHogClient, "postHogClient") - .mockImplementation(() => mockPostHogClient); - - capture({ - distinctId: "abc", - event: "ate pizza", - }); - - expect(mockPostHogClient.capture).toHaveBeenCalledWith({ - distinctId: "abc", - event: "ate pizza", - properties: { - mailing_version: "mock-mailing-version", - }, - }); - }); -}); diff --git a/packages/cli/src/util/postHog/client.ts b/packages/cli/src/util/postHog/client.ts deleted file mode 100644 index fc46920c8..000000000 --- a/packages/cli/src/util/postHog/client.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { PostHog } from "posthog-node"; -import { POSTHOG_API_KEY } from "./posthogApiKey"; - -let client: PostHog | undefined; - -export function postHogClient(): PostHog | undefined { - if (process.env.MM_E2E || process.env.CI || "test" === process.env.NODE_ENV) - return; - - if (undefined === POSTHOG_API_KEY) { - console.error("POSTHOG_API_KEY is undefined"); - return; - } - - if (undefined === client) { - client = new PostHog(POSTHOG_API_KEY, { - host: "https://app.posthog.com", - }); - } - - return client; -} - -// readonly version of postHogClient -export function getPostHogClient(): PostHog | undefined { - return client; -} diff --git a/packages/cli/src/util/postHog/index.ts b/packages/cli/src/util/postHog/index.ts deleted file mode 100644 index a1d5eec53..000000000 --- a/packages/cli/src/util/postHog/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { debug } from "../serverLogger"; -import { getConfig } from "../moduleManifestUtil"; -import { getGeneratedAnonymousId } from "../config/anonymousId"; - -import { postHogClient, getPostHogClient } from "./client"; -import { MAILING_VERSION } from "../../const/mailingVersion"; - -// ** modified from posthog-node -interface IdentifyMessageV1 { - distinctId?: string; - properties?: Record; -} - -interface EventMessageV1 extends IdentifyMessageV1 { - event: string; - groups?: Record; - sendFeatureFlags?: boolean; -} -export function capture(options: EventMessageV1) { - const config = getConfig(); - - debug( - `options.distinctId was ${options.distinctId} and config.anonymousId was ${ - config.anonymousId - } and getGeneratedAnonymousId() returned ${getGeneratedAnonymousId()}` - ); - - const distinctId = - options.distinctId || config.anonymousId || getGeneratedAnonymousId(); - - if (!distinctId) { - debug(`capture is returning early because distinctId was ${distinctId}`); - return; - } - - const captureOpts = { - ...options, - distinctId, - properties: { - mailing_version: MAILING_VERSION, - ...options.properties, - }, - }; - - debug(`calling capture with ${JSON.stringify(captureOpts)}`); - - if (process.env.MM_DEV) { - debug("returning early from capture because MM_DEV is set"); - return; - } - - return postHogClient()?.capture(captureOpts); -} - -export async function shutdown() { - const client = getPostHogClient(); - if (client) { - debug("calling postHog shutdown"); - try { - await client.shutdownAsync(); - } catch (e) { - debug(`error shutting down postHog client: ${e}`); - } - } else { - debug("skipping postHog client shutdown because it was never instantiated"); - } -} diff --git a/packages/cli/src/util/postHog/posthogApiKey.ts b/packages/cli/src/util/postHog/posthogApiKey.ts deleted file mode 100644 index a790d352c..000000000 --- a/packages/cli/src/util/postHog/posthogApiKey.ts +++ /dev/null @@ -1 +0,0 @@ -export const POSTHOG_API_KEY = "phc_7ZFYSlHCG9Fo6a7do1BC4tUDx1DzuceaymIxZAfPUVg"; diff --git a/packages/core/package.json b/packages/core/package.json index ceb3dbc61..b1ca4356f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -17,6 +17,6 @@ "node-fetch": "^2.6.7", "node-html-parser": "^6.1.1", "open": "^8.4.0", - "posthog-node": "^2.0.2" + "posthog-node": "^2.2.3" } } diff --git a/packages/core/src/__test__/index.test.tsx b/packages/core/src/__test__/index.test.tsx index 126431839..cf3c9822c 100644 --- a/packages/core/src/__test__/index.test.tsx +++ b/packages/core/src/__test__/index.test.tsx @@ -6,16 +6,13 @@ import { clearTestMailQueue, ComponentMail, BuildSendMailOptions, - EMAIL_PREFERENCES_URL, } from ".."; import { Mjml, MjmlBody, MjmlRaw } from "@faire/mjml-react"; import fetch from "node-fetch"; import open from "open"; -import * as postHog from "../util/postHog"; import * as serverLogger from "../util/serverLogger"; import fsExtra from "fs-extra"; -jest.mock("../util/postHog"); jest.mock("node-fetch"); jest.mock("open"); @@ -274,20 +271,16 @@ describe("index", () => { describe("sendMail", () => { let mockSendMail = jest.fn(); - let mockCapture = jest.fn(); beforeEach(() => { mockSendMail = jest.fn(); - mockCapture = jest.fn(); jest.spyOn(transport, "sendMail").mockImplementation(mockSendMail); - jest.spyOn(postHog, "capture").mockImplementation(mockCapture); jest.spyOn(fsExtra, "readFileSync").mockImplementation((path) => { if (/mailing\.config\.json/.test(path.toString())) { return JSON.stringify({ typescript: true, emailsDir: "./emails", outDir: "./previews_html", - anonymousId: "anonymousId", }); } else { return ""; @@ -318,259 +311,6 @@ describe("index", () => { to: ["ok@ok.com"], }); }); - - it("calls calls capture with correct arguments", async () => { - const sendMail = buildSendMail({ - transport, - defaultFrom: "from@mailing.dev", - configPath: "./mailing.config.json", - }); - await sendMail({ - to: ["ok@ok.com"], - from: "ok@ok.com", - subject: "hello", - text: "ok", - html: "ok", - dangerouslyForceDeliver: true, - }); - expect(mockCapture).toHaveBeenCalled(); - expect(mockCapture).toHaveBeenCalledWith({ - distinctId: "unknown", - event: "mail sent", - properties: { - analyticsEnabled: false, - recipientCount: 1, - }, - }); - }); - - describe("analyticsEnabled", () => { - beforeEach(() => { - process.env.MAILING_API_URL = "https://mailing.test"; - process.env.MAILING_API_KEY = "test_key"; - }); - - it("hits message create api with correct arguments", async () => { - const res = new Response( - JSON.stringify({ message: { id: "message-1234" } }), - { - status: 200, - } - ); - - (fetch as unknown as jest.Mock).mockResolvedValueOnce( - Promise.resolve(res) - ); - - const sendMail = buildSendMail({ - transport, - defaultFrom: "from@mailing.dev", - configPath: "./mailing.config.json", - }); - - await sendMail({ - to: ["ok@ok.com"], - from: "ok@ok.com", - subject: "hello", - text: "ok", - html: "ok", - dangerouslyForceDeliver: true, - }); - - expect(fetch).toHaveBeenCalled(); - expect(fetch).toHaveBeenCalledWith( - "https://mailing.test/api/messages", - { - headers: { - "Content-Type": "application/json", - "x-api-key": "test_key", - }, - method: "POST", - body: JSON.stringify({ - skipUnsubscribeChecks: true, - anonymousId: "unknown", - to: ["ok@ok.com"], - from: "ok@ok.com", - subject: "hello", - text: "ok", - html: "ok", - }), - } - ); - expect(mockSendMail).toHaveBeenCalled(); - }); - - it("does not send an email if message create api returned a non-200 response", async () => { - const res = new Response("internal server error", { - status: 500, - }); - - (fetch as unknown as jest.Mock).mockResolvedValueOnce( - Promise.resolve(res) - ); - - const errorSpy = jest - .spyOn(serverLogger, "error") - .mockImplementation(jest.fn()); - - const sendMail = buildSendMail({ - transport, - defaultFrom: "from@mailing.dev", - configPath: "./mailing.config.json", - }); - - await sendMail({ - to: ["ok@ok.com"], - from: "ok@ok.com", - subject: "hello", - text: "ok", - html: "ok", - dangerouslyForceDeliver: true, - }); - - expect(fetch).toHaveBeenCalled(); - expect(fetch).toHaveBeenCalledWith( - "https://mailing.test/api/messages", - { - headers: { - "Content-Type": "application/json", - "x-api-key": "test_key", - }, - method: "POST", - body: JSON.stringify({ - skipUnsubscribeChecks: true, - anonymousId: "unknown", - to: ["ok@ok.com"], - from: "ok@ok.com", - subject: "hello", - text: "ok", - html: "ok", - }), - } - ); - - // it is unsafe to send the email because the user may have unsubscribed, we don't know - // also this is the mechanism that adds the unsubscribe link, so we are not able to include that - expect(mockSendMail).not.toHaveBeenCalled(); - expect(errorSpy).toHaveBeenCalled(); - }); - - describe("lists", () => { - it("should send an email to a user that is subscribed", async () => { - const res = new Response( - JSON.stringify({ message: { id: "message-1234" } }), - { - status: 200, - } - ); - - (fetch as unknown as jest.Mock).mockResolvedValueOnce( - Promise.resolve(res) - ); - - const sendMail = buildSendMail({ - transport, - defaultFrom: "replace@me.with.your.com", - configPath: "./mailing.config.json", - }); - - const email = "test@test.com"; - const html = `See the bottom of this email for an unsubscribe link
Unsubscribe`; - const listName = "mylista4290"; - await sendMail({ - to: email, - from: "ok@ok.com", - subject: "hello", - listName, - dangerouslyForceDeliver: true, - html, - }); - - expect(fetch).toHaveBeenCalled(); - expect(fetch).toHaveBeenCalledWith( - "https://mailing.test/api/messages", - { - headers: { - "Content-Type": "application/json", - "x-api-key": "test_key", - }, - method: "POST", - body: JSON.stringify({ - skipUnsubscribeChecks: false, - anonymousId: "unknown", - to: email, - from: "ok@ok.com", - subject: "hello", - html, - listName, - }), - } - ); - - // it is unsafe to send the email because the user may have unsubscribed, we don't know - // also this is the mechanism that adds the unsubscribe link, so we are not able to include that - expect(mockSendMail).toHaveBeenCalled(); - }); - - it("should not send an email to a user that is unsubscribed", async () => { - const res = new Response( - JSON.stringify({ - error: "user is not subscribed to either list", - }), - { - status: 200, - } - ); - - (fetch as unknown as jest.Mock).mockResolvedValueOnce( - Promise.resolve(res) - ); - - const sendMail = buildSendMail({ - transport, - defaultFrom: "replace@me.with.your.com", - configPath: "./mailing.config.json", - }); - - const email = "test@test.com"; - const html = `See the bottom of this email for an unsubscribe link
Unsubscribe`; - const listName = "mylista4290"; - await sendMail({ - to: email, - from: "ok@ok.com", - subject: "hello", - listName, - dangerouslyForceDeliver: true, - html, - }); - - expect(fetch).toHaveBeenCalled(); - expect(fetch).toHaveBeenCalledWith( - "https://mailing.test/api/messages", - { - headers: { - "Content-Type": "application/json", - "x-api-key": "test_key", - }, - method: "POST", - body: JSON.stringify({ - skipUnsubscribeChecks: false, - anonymousId: "unknown", - to: email, - from: "ok@ok.com", - subject: "hello", - html, - listName, - }), - } - ); - - // it is unsafe to send the email because the user may have unsubscribed, we don't know - // also this is the mechanism that adds the unsubscribe link, so we are not able to include that - expect(mockSendMail).not.toHaveBeenCalled(); - }); - }); - }); }); }); @@ -625,44 +365,4 @@ describe("index", () => { }); }); }); - - describe("analyticsEnabled: true", () => { - describe("with analytics enabled, using magic 'testApiKey'", () => { - const OG_MAILING_API_URL = process.env.MAILING_API_URL; - const OG_MAILING_API_KEY = process.env.MAILING_API_KEY; - - beforeAll(() => { - process.env.MAILING_API_URL = "http://localhost:3883"; - // testApiKey is a magic string that bypasses the api key check when MAILING_INTEGRATION_TEST is set to true - process.env.MAILING_API_KEY = "testApiKey"; - }); - - afterAll(() => { - process.env.MAILING_API_URL = OG_MAILING_API_URL; - process.env.MAILING_API_KEY = OG_MAILING_API_KEY; - }); - - it("should throw an error when called with a list using a template that does not have an unsubscribe link", async () => { - const sendMail = buildSendMail({ - transport, - defaultFrom: "replace@me.with.your.com", - configPath: "./mailing.config.json", - }); - - const email = "testListSpecifiedButNoUnsub@test.com"; - const callSendMail = async () => - await sendMail({ - to: email, - listName: "mylista4290", - dangerouslyForceDeliver: true, - html: "No unsubscribe link here", - subject: "hello", - }); - - await expect(callSendMail).rejects.toThrowError( - "Templates sent to a list must include an unsubscribe link. Add an unsubscribe link or remove the list parameter from your sendMail call." - ); - }); - }); - }); }); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4f2652b8d..bf9addf33 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,7 +4,6 @@ import fs from "fs-extra"; import { render } from "./mjml"; import { error, log } from "./util/serverLogger"; import fetch from "node-fetch"; -import { capture } from "./util/postHog"; import instrumentHtml from "./util/instrumentHtml"; class MalformedInputError extends Error { @@ -87,12 +86,6 @@ export function buildSendMail(options: BuildSendMailOptions) { "sendMail requires either html or a component" ); - let anonymousId = "unknown"; - - if ((sendMail as any).config) { - anonymousId = (sendMail as any).config.anonymousId; - } - const { NODE_ENV, MAILING_API_URL, MAILING_API_KEY } = process.env; const { component, @@ -183,7 +176,6 @@ export function buildSendMail(options: BuildSendMailOptions) { }, body: JSON.stringify({ skipUnsubscribeChecks, - anonymousId, templateName: templateName || derivedTemplateName, previewName, ...mailOptions, @@ -239,17 +231,6 @@ export function buildSendMail(options: BuildSendMailOptions) { const response = integrationTestMode ? "delivered!" : await options.transport.sendMail(mailOptions); - await capture({ - event: "mail sent", - distinctId: anonymousId, - properties: { - recipientCount: - Array(mailOptions.to).filter(Boolean).length + - Array(mailOptions.cc).filter(Boolean).length + - Array(mailOptions.bcc).filter(Boolean).length, - analyticsEnabled, - }, - }); return response; }; diff --git a/packages/core/src/util/postHog/__test__/indexMockMailingCoreVersion.test.ts b/packages/core/src/util/postHog/__test__/indexMockMailingCoreVersion.test.ts deleted file mode 100644 index 1f8921f56..000000000 --- a/packages/core/src/util/postHog/__test__/indexMockMailingCoreVersion.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { capture } from ".."; -import * as postHogClient from "../client"; -import { PostHog } from "posthog-node"; - -jest.mock("../../../const/mailingCoreVersion", () => ({ - MAILING_CORE_VERSION: "mock-mailing-core-version", -})); - -describe("posthog", () => { - const MM_DEV_OG = process.env.MM_DEV; - const MM_DEV_E2E_OG = process.env.MM_E2E; - - let mockPostHogClient: PostHog; - beforeEach(() => { - mockPostHogClient = { capture: jest.fn() } as unknown as PostHog; - jest.restoreAllMocks(); - delete process.env.MM_DEV; - delete process.env.MM_E2E; - }); - - afterEach(() => { - process.env.MM_DEV = MM_DEV_OG; - process.env.MM_E2E = MM_DEV_E2E_OG; - }); - - it("should include MAILING_CORE_VERSION because it is defined", async () => { - jest - .spyOn(postHogClient, "postHogClient") - .mockImplementation(() => mockPostHogClient); - - await capture({ - distinctId: "abc", - event: "ate pizza", - }); - - expect(mockPostHogClient.capture).toHaveBeenCalledWith({ - distinctId: "abc", - event: "ate pizza", - properties: { - mailing_core_version: "mock-mailing-core-version", - }, - }); - }); -}); diff --git a/packages/core/src/util/postHog/client.ts b/packages/core/src/util/postHog/client.ts deleted file mode 100644 index 6e21a2aa0..000000000 --- a/packages/core/src/util/postHog/client.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { PostHog } from "posthog-node"; -import { debug } from "../serverLogger"; - -const POSTHOG_API_KEY = "phc_7ZFYSlHCG9Fo6a7do1BC4tUDx1DzuceaymIxZAfPUVg"; - -let client: PostHog | undefined; - -export function postHogClient(): PostHog | undefined { - if (process.env.MM_E2E || process.env.CI || "test" === process.env.NODE_ENV) - return; - - if (undefined === POSTHOG_API_KEY) { - debug("POSTHOG_API_KEY is undefined"); - return; - } - - if (undefined === client) { - client = new PostHog(POSTHOG_API_KEY, { - host: "https://app.posthog.com", - }); - } - - return client; -} - -// readonly version of postHogClient -export function getPostHogClient(): PostHog | undefined { - return client; -} diff --git a/packages/core/src/util/postHog/index.ts b/packages/core/src/util/postHog/index.ts deleted file mode 100644 index da0007d64..000000000 --- a/packages/core/src/util/postHog/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { MAILING_CORE_VERSION } from "../../const/mailingCoreVersion"; -import { debug } from "../serverLogger"; -import { postHogClient } from "./client"; - -// ** modified from posthog-node -interface IdentifyMessageV1 { - distinctId: string; - properties?: Record; -} - -interface EventMessageV1 extends IdentifyMessageV1 { - event: string; - groups?: Record; - sendFeatureFlags?: boolean; -} -export async function capture(options: EventMessageV1) { - const distinctId = options.distinctId; - - const captureOpts = { - ...options, - distinctId, - properties: { - mailing_core_version: MAILING_CORE_VERSION, - ...options.properties, - }, - }; - - debug(`calling capture with ${JSON.stringify(captureOpts)}`); - - if (process.env.MM_DEV) { - debug("returning early from capture because MM_DEV is set"); - return; - } - - try { - const client = postHogClient(); - if (!client) return; - - if (process.env.NODE_ENV === "production") { - client.capture(captureOpts); - } else if (process.env.NODE_ENV === "test") { - // call capture if it has been mocked - const capture = client.capture as typeof client.capture & { - mock?: any; - }; - if (capture.mock) capture(captureOpts); - } else { - debug( - `returning early from capture because NODE_ENV is ${process.env.NODE_ENV}` - ); - } - - await client.shutdownAsync(); - } catch (e) { - debug("posthog capture error", e); - } -} diff --git a/scripts/release b/scripts/release index 21429fd61..f37a6ea3f 100755 --- a/scripts/release +++ b/scripts/release @@ -5,12 +5,6 @@ if [[ -f .env ]]; then source .env fi -# throw an error if $POSTHOG_API_KEY is not set -if [[ -z "$POSTHOG_API_KEY" ]]; then - echo "Must provide POSTHOG_API_KEY in environment" 1>&2 - exit 1 -fi - # set variables MAILING_VERSION and MAILING_CORE_VERSION to the value of the "version" attribute from each package.json MAILING_VERSION=$(awk -F'"' '/"version": ".+"/{ print $4; exit; }' packages/cli/package.json) MAILING_CORE_VERSION=$(awk -F'"' '/"version": ".+"/{ print $4; exit; }' packages/core/package.json) @@ -30,9 +24,6 @@ fi ## # Search and replace in src directories -- these will be undone in the post-release script -# search cli and core src and replace "process.env.POSTHOG_API_KEY" with "\"${process.env.POSTHOG_API_KEY}\"" -find 'packages/cli/src' 'packages/core/src' -type f \( -name '*.ts' -o -name '*.tsx' \) -exec sed -i '' "s/process\.env\.POSTHOG_API_KEY/\"$POSTHOG_API_KEY\"/g" {} + - # search cli src and replace "process.env.MAILING_VERSION" with "\"${process.env.MAILING_VERSION}\"" find 'packages/cli/src' -type f \( -name '*.ts' -o -name '*.tsx' \) -exec sed -i '' "s/MAILING_VERSION = process\.env\.MAILING_VERSION/MAILING_VERSION = \"$MAILING_VERSION\"/g" {} + @@ -46,9 +37,6 @@ yarn changeset publish ## # Undo search and replace in src directories from above -# search cli and core src and replace "process.env.POSTHOG_API_KEY" with "\"${process.env.POSTHOG_API_KEY}\"" -find 'packages/cli/src' 'packages/core/src' -type f \( -name '*.ts' -o -name '*.tsx' \) -exec sed -i '' "s/\"$POSTHOG_API_KEY\"/process\.env\.POSTHOG_API_KEY/g" {} + - # search cli src and replace "process.env.MAILING_VERSION" with "\"${process.env.MAILING_VERSION}\"" find 'packages/cli/src' -type f \( -name '*.ts' -o -name '*.tsx' \) -exec sed -i '' "s/MAILING_VERSION = \"$MAILING_VERSION\"/MAILING_VERSION = process\.env\.MAILING_VERSION/g" {} + diff --git a/yarn.lock b/yarn.lock index dd7106a9f..2dfd17797 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5301,11 +5301,16 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.14.0, follow-redirects@^1.14.9: +follow-redirects@^1.14.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.14.9: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -8964,17 +8969,10 @@ postcss@^8.4.18: picocolors "^1.0.0" source-map-js "^1.0.2" -posthog-node@^2.0.2: - version "2.1.0" - resolved "https://registry.yarnpkg.com/posthog-node/-/posthog-node-2.1.0.tgz#d99acda28b208cbeefed2e6cd0ddfa8cc2d79910" - integrity sha512-xr56mZRQo7rnL2YdwbipcxTZeyi5dcI6IM4++wIN7JLYwinrJYcQv01nan4gU4kMy33Qz5qT6boWMQRwpKZJVQ== - dependencies: - axios "^0.27.0" - posthog-node@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/posthog-node/-/posthog-node-2.2.3.tgz#9fda0dfe92dedc0716b87a68a26c05eed95bdd5a" - integrity sha512-dYlLZhrDus+uRov/Hh+EiRlMoMhRKchNjNa7mNE2iWmKg/ryOTipf0XYKS9UKdki7aU1NzWFhnLe11HF615XuA== + version "2.6.0" + resolved "https://registry.yarnpkg.com/posthog-node/-/posthog-node-2.6.0.tgz#63665d85806a20ea023ea6ce9ac4e114e082a351" + integrity sha512-/BiFw/jwdP0uJSRAIoYqLoBTjZ612xv74b1L/a3T/p1nJVL8e0OrHuxbJW56c6WVW/IKm9gBF/zhbqfaz0XgJQ== dependencies: axios "^0.27.0" From eb852ae4a53570bdd8e26dcea6f455f0da56a2fc Mon Sep 17 00:00:00 2001 From: psugihara Date: Mon, 4 Dec 2023 12:05:41 -0800 Subject: [PATCH 2/7] rm email capture --- packages/cli/src/commands/init.ts | 90 +++++++++---------------------- 1 file changed, 26 insertions(+), 64 deletions(-) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 117c4bebe..991969148 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1,9 +1,6 @@ -import prompts from "prompts"; -import fetch from "node-fetch"; import { existsSync } from "fs-extra"; import { ArgumentsCamelCase } from "yargs"; -import { error, log } from "../util/serverLogger"; -import { getMailingAPIBaseURL } from "../util/paths"; +import { log } from "../util/serverLogger"; import { generateEmailsDirectory } from "../util/generators"; import { handler as previewHandler, PreviewArgs } from "./preview/preview"; import { defaults } from "../util/config"; @@ -52,68 +49,33 @@ export const builder = { }, }; -export const handler = buildHandler( - async (argv: InitArguments) => { - if (typeof argv.port !== "number") - throw new Error("port option is not set"); - if (typeof argv.typescript !== "boolean") - throw new Error("typescript option not set"); - if (undefined === argv.emailsDir) - throw new Error("emailsDir option not set"); +export const handler = buildHandler(async (argv: InitArguments) => { + if (typeof argv.port !== "number") throw new Error("port option is not set"); + if (typeof argv.typescript !== "boolean") + throw new Error("typescript option not set"); + if (undefined === argv.emailsDir) throw new Error("emailsDir option not set"); - if (existsSync(resolve(argv.emailsDir, "previews"))) { - log("Using emails directory", argv.emailsDir); - } else { - const options = { - isTypescript: argv.typescript, - emailsDir: argv.emailsDir, - }; - await generateEmailsDirectory(options); - - if (argv.scaffoldOnly) { - return; - } - - if (!argv.quiet) { - const emailResponse = await prompts({ - type: "text", - name: "email", - message: - "enter your email for occasional updates about mailing (optional)", - }); - const { email } = emailResponse; - if (email?.length > 0) { - log("great, talk soon"); - try { - const url = `${getMailingAPIBaseURL()}/api/newsletterSubscribers`; - - void fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ email }), - }); - } catch (e) { - error(e); - } - } else { - log("ok, no problem"); - } - } - } - - const previewHandlerArgv: PreviewArgs = { - port: argv.port, - quiet: argv.quiet, + if (existsSync(resolve(argv.emailsDir, "previews"))) { + log("Using emails directory", argv.emailsDir); + } else { + const options = { + isTypescript: argv.typescript, emailsDir: argv.emailsDir, - $0: argv.$0, - _: argv._, }; + await generateEmailsDirectory(options); - await previewHandler(previewHandlerArgv); - }, - { - captureOptions: () => { - return { event: "init invoked" }; - }, + if (argv.scaffoldOnly) { + return; + } } -); + + const previewHandlerArgv: PreviewArgs = { + port: argv.port, + quiet: argv.quiet, + emailsDir: argv.emailsDir, + $0: argv.$0, + _: argv._, + }; + + await previewHandler(previewHandlerArgv); +}); From 8e90ad240f55dce22f12bd6da82c9f1d2d543f99 Mon Sep 17 00:00:00 2001 From: psugihara Date: Mon, 4 Dec 2023 12:09:18 -0800 Subject: [PATCH 3/7] fix tests --- packages/cli/src/commands/exportPreviews.ts | 203 +++++++++--------- packages/cli/src/commands/preview/preview.ts | 17 +- packages/cli/src/commands/server.ts | 66 +++--- .../src/util/__test__/buildHandler.test.ts | 6 +- 4 files changed, 133 insertions(+), 159 deletions(-) diff --git a/packages/cli/src/commands/exportPreviews.ts b/packages/cli/src/commands/exportPreviews.ts index cdd1201d8..4caa42693 100644 --- a/packages/cli/src/commands/exportPreviews.ts +++ b/packages/cli/src/commands/exportPreviews.ts @@ -60,113 +60,104 @@ export function previewFilename(moduleName: string, functionName: string) { ); } -export const handler = buildHandler( - async (argv: ExportPreviewsArgs) => { - if (!argv.outDir) throw new Error("outDir option is not set"); - - const outDir = argv.outDir; - - if (typeof outDir !== "string") { - error("please specify an outDir like --outDir ./html"); - return; - } - - if (undefined === argv.emailsDir) { - error("please specific an emailsDir like --emailsDir ./emails"); - return; - } - - const previewsPath = getPreviewsDirectory(argv.emailsDir); - if (!previewsPath) { - error( - "Could not find emails directory. Have you initialized the project with `mailing init`?" - ); - return; - } - - registerRequireHooks(); - - const previewText = argv.minify ? "minified preview html" : "preview html"; - - let count = 0; - - const lint: { [filename: string]: HtmlLintError[] } = {}; - const toWrite: Array<() => Promise> = []; - const filenames: string[] = []; - - const previewRenders = readdirSync(previewsPath) - .filter((path) => !/^\./.test(path)) - .flatMap((p) => { - const previewPath = resolve(previewsPath, p); - const previewModule = require(previewPath); - - return Object.keys(require(previewPath)).flatMap( - async (previewFunction) => { - const filename = previewFilename(p, previewFunction); - count++; - - const { html, errors, htmlLint } = render( - await previewModule[previewFunction]() - ); - if (errors.length) { - error(`MJML errors rendering ${filename}:`, errors); - } - - if (htmlLint.length && !argv.skipLint) { - lint[filename] = htmlLint; - } - - const minifyConfig = { - collapseWhitespace: true, - minifyCSS: false, - caseSensitive: true, - removeEmptyAttributes: true, - }; - - const outHtml = argv.minify - ? await minify(html, minifyConfig) - : html; - filenames.push(filename); - toWrite.push(async () => - outputFile(resolve(outDir, filename), outHtml) - ); +export const handler = buildHandler(async (argv: ExportPreviewsArgs) => { + if (!argv.outDir) throw new Error("outDir option is not set"); + + const outDir = argv.outDir; + + if (typeof outDir !== "string") { + error("please specify an outDir like --outDir ./html"); + return; + } + + if (undefined === argv.emailsDir) { + error("please specific an emailsDir like --emailsDir ./emails"); + return; + } + + const previewsPath = getPreviewsDirectory(argv.emailsDir); + if (!previewsPath) { + error( + "Could not find emails directory. Have you initialized the project with `mailing init`?" + ); + return; + } + + registerRequireHooks(); + + const previewText = argv.minify ? "minified preview html" : "preview html"; + + let count = 0; + + const lint: { [filename: string]: HtmlLintError[] } = {}; + const toWrite: Array<() => Promise> = []; + const filenames: string[] = []; + + const previewRenders = readdirSync(previewsPath) + .filter((path) => !/^\./.test(path)) + .flatMap((p) => { + const previewPath = resolve(previewsPath, p); + const previewModule = require(previewPath); + + return Object.keys(require(previewPath)).flatMap( + async (previewFunction) => { + const filename = previewFilename(p, previewFunction); + count++; + + const { html, errors, htmlLint } = render( + await previewModule[previewFunction]() + ); + if (errors.length) { + error(`MJML errors rendering ${filename}:`, errors); } - ); - }); - await Promise.all(previewRenders); - - const lintCount = Object.keys(lint).length; - if (lintCount) { - error( - lintCount > 1 - ? `Aborted because ${lintCount} files have lint errors:` - : `Aborted because 1 file has lint errors:`, - "\n\n", - Object.entries(lint) - .map( - ([filename, errors]) => - `${filename}\n${errors - .map((e, i) => ` ${i + 1}. ${e.message}`) - .join("\n\n")}` - ) - .join("\n\n"), - "\n\n" - ); - return; - } + if (htmlLint.length && !argv.skipLint) { + lint[filename] = htmlLint; + } - log(`Exporting ${previewText} to`); - log(`${outDir}/`); - await Promise.all(toWrite.map((f) => f())); - for (const f of filenames.sort()) { - log(` |-- ${f}`); - } - log(`✅ Processed ${count} previews\n`); - }, - { - captureOptions: () => { - return { event: "export-previews invoked" }; - }, + const minifyConfig = { + collapseWhitespace: true, + minifyCSS: false, + caseSensitive: true, + removeEmptyAttributes: true, + }; + + const outHtml = argv.minify ? await minify(html, minifyConfig) : html; + filenames.push(filename); + toWrite.push(async () => + outputFile(resolve(outDir, filename), outHtml) + ); + } + ); + }); + await Promise.all(previewRenders); + + const lintCount = Object.keys(lint).length; + if (lintCount) { + error( + lintCount > 1 + ? `Aborted because ${lintCount} files have lint errors:` + : `Aborted because 1 file has lint errors:`, + "\n\n", + Object.entries(lint) + .map( + ([filename, errors]) => + `${filename}\n${errors + .map((e, i) => ` ${i + 1}. ${e.message}`) + .join("\n\n")}` + ) + .join("\n\n"), + "\n\n" + ); + + return; + } + + log(`Exporting ${previewText} to`); + log(`${outDir}/`); + await Promise.all(toWrite.map((f) => f())); + for (const f of filenames.sort()) { + log(` |-- ${f}`); } -); + log(`✅ Processed ${count} previews\n`); +}); diff --git a/packages/cli/src/commands/preview/preview.ts b/packages/cli/src/commands/preview/preview.ts index f3e86bf4e..f979fc490 100644 --- a/packages/cli/src/commands/preview/preview.ts +++ b/packages/cli/src/commands/preview/preview.ts @@ -29,16 +29,9 @@ export const builder = { }, }; -export const handler = buildHandler( - async (argv: PreviewArgs) => { - if (undefined === argv.port) throw new Error("port option is not set"); - if (undefined === argv.quiet) throw new Error("quiet option is not set"); +export const handler = buildHandler(async (argv: PreviewArgs) => { + if (undefined === argv.port) throw new Error("port option is not set"); + if (undefined === argv.quiet) throw new Error("quiet option is not set"); - await startPreviewServer(); - }, - { - captureOptions: () => { - return { event: "preview invoked" }; - }, - } -); + await startPreviewServer(); +}); diff --git a/packages/cli/src/commands/server.ts b/packages/cli/src/commands/server.ts index bc438cd7f..651ca9950 100644 --- a/packages/cli/src/commands/server.ts +++ b/packages/cli/src/commands/server.ts @@ -38,50 +38,40 @@ export const builder = { }, }; -export const handler = buildHandler( - async (argv: ServerArguments) => { - if (undefined === argv.port) throw new Error("port option is not set"); - if (undefined === argv.quiet) throw new Error("quiet option is not set"); - if (undefined === argv.emailsDir) - throw new Error("emailsDir option is not set"); +export const handler = buildHandler(async (argv: ServerArguments) => { + if (undefined === argv.port) throw new Error("port option is not set"); + if (undefined === argv.quiet) throw new Error("quiet option is not set"); + if (undefined === argv.emailsDir) + throw new Error("emailsDir option is not set"); - log("bootstrapping your Mailing server in .mailing..."); - await bootstrapMailingDir(); - await linkEmailsDirectory(argv.emailsDir); + log("bootstrapping your Mailing server in .mailing..."); + await bootstrapMailingDir(); + await linkEmailsDirectory(argv.emailsDir); - const shouldStart = argv.subcommand === "start" || !argv.subcommand; - const shouldBuild = argv.subcommand === "build" || !argv.subcommand; + const shouldStart = argv.subcommand === "start" || !argv.subcommand; + const shouldBuild = argv.subcommand === "build" || !argv.subcommand; - // "build" subcommand + default - if (shouldBuild) { - log("building .mailing..."); + // "build" subcommand + default + if (shouldBuild) { + log("building .mailing..."); - execSync("cd .mailing && npx prisma generate", { - stdio: "inherit", - }); - - if (process.env.MAILING_DATABASE_URL) - execSync("cd .mailing && npx prisma migrate deploy", { - stdio: "inherit", - }); + execSync("cd .mailing && npx prisma generate", { + stdio: "inherit", + }); - execSync("cd .mailing && npx next build", { + if (process.env.MAILING_DATABASE_URL) + execSync("cd .mailing && npx prisma migrate deploy", { stdio: "inherit", }); - } - // "start" subcommand + default - if (shouldStart) { - log("starting .mailing..."); - execSync("npx next start .mailing", { stdio: "inherit" }); - } - }, - { - captureOptions: (argv) => { - return { - event: "server invoked", - properties: { subcommand: argv.subcommand }, - }; - }, + execSync("cd .mailing && npx next build", { + stdio: "inherit", + }); + } + + // "start" subcommand + default + if (shouldStart) { + log("starting .mailing..."); + execSync("npx next start .mailing", { stdio: "inherit" }); } -); +}); diff --git a/packages/cli/src/util/__test__/buildHandler.test.ts b/packages/cli/src/util/__test__/buildHandler.test.ts index 1fa9ddc70..7c96bf213 100644 --- a/packages/cli/src/util/__test__/buildHandler.test.ts +++ b/packages/cli/src/util/__test__/buildHandler.test.ts @@ -14,7 +14,7 @@ describe("buildHandler", () => { describe("shared guards and setup", () => { it("returns early if ./package.json is not found", async () => { - const handler = buildHandler(async () => {}, {}); + const handler = buildHandler(async () => {}); const argv = { emailsDir: "./emails", }; @@ -37,7 +37,7 @@ describe("buildHandler", () => { }); it("throws an error if emailsDir is not specified in argv", async () => { - const handler = buildHandler(async () => {}, {}); + const handler = buildHandler(async () => {}); const argv = {}; await expect(async () => { @@ -46,7 +46,7 @@ describe("buildHandler", () => { }); it("calls setConfig and writeDefaultConfigFile", async () => { - const handler = buildHandler(async () => {}, {}); + const handler = buildHandler(async () => {}); const argv = { emailsDir: "./emails", port: 3883, From 9d78de69fc020035f8996a6c789c9debb0c53c95 Mon Sep 17 00:00:00 2001 From: psugihara Date: Mon, 4 Dec 2023 12:20:53 -0800 Subject: [PATCH 4/7] try unknown cast --- packages/cli/src/util/tailwind.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/util/tailwind.ts b/packages/cli/src/util/tailwind.ts index c29f19d37..fdebb79f6 100644 --- a/packages/cli/src/util/tailwind.ts +++ b/packages/cli/src/util/tailwind.ts @@ -3,5 +3,8 @@ import tailwindConfig from "../../tailwind.config.js"; const fullConfig = resolveConfig(tailwindConfig); -export const colors = fullConfig.theme?.colors as Record; +export const colors = fullConfig.theme?.colors as unknown as Record< + string, + string +>; export default fullConfig; From a094169f237870bb355670c66d48ba88b3595fc8 Mon Sep 17 00:00:00 2001 From: psugihara Date: Mon, 4 Dec 2023 12:46:37 -0800 Subject: [PATCH 5/7] update remix template --- e2e/app/remix_ts.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/app/remix_ts.rb b/e2e/app/remix_ts.rb index 720021fff..267405681 100644 --- a/e2e/app/remix_ts.rb +++ b/e2e/app/remix_ts.rb @@ -14,7 +14,7 @@ def initialize(root_dir, *args) def yarn_create! Dir.chdir(root_dir) do # install with the "remix" template - system_quiet('yarn create remix . --template=remix --typescript --install') + system_quiet('yarn create remix . --template=remix-run/remix/templates/remix --typescript --install') ## variation: indie-stack is a different remix template that people use # system_quiet("yarn create remix . --template=remix-run/indie-stack --typescript --install") From a550fc09af9e1d3956ee6d575a3b2a120473a866 Mon Sep 17 00:00:00 2001 From: psugihara Date: Mon, 4 Dec 2023 13:17:48 -0800 Subject: [PATCH 6/7] try latest cypress action --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 5a89ca1cc..28a5433d0 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -39,7 +39,7 @@ jobs: run: yarn jest --rootDir=mailing_tests/jest --config mailing_tests/jest/jest.config.json working-directory: /tmp/mailing_e2e/ci - name: Run ${{ matrix.e2e-config }} cypress tests - uses: cypress-io/github-action@v4 + uses: cypress-io/github-action@v6 env: DEBUG: "@cypress/github-action" with: From 9ebfa24e6f673574e9aebb63ee85eadc7b3ed09f Mon Sep 17 00:00:00 2001 From: psugihara Date: Mon, 4 Dec 2023 13:25:31 -0800 Subject: [PATCH 7/7] changeset --- .changeset/rich-adults-train.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/rich-adults-train.md diff --git a/.changeset/rich-adults-train.md b/.changeset/rich-adults-train.md new file mode 100644 index 000000000..a2eedc362 --- /dev/null +++ b/.changeset/rich-adults-train.md @@ -0,0 +1,6 @@ +--- +"mailing": minor +"mailing-core": minor +--- + +remove anonymous telemetry and email collection