@hammadj/better-auth-telemetry 1.5.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,203 @@
1
+ import type { BetterAuthOptions } from "@better-auth/core";
2
+ import type { TelemetryContext } from "../types";
3
+
4
+ export function getTelemetryAuthConfig(
5
+ options: BetterAuthOptions,
6
+ context?: TelemetryContext | undefined,
7
+ ) {
8
+ return {
9
+ database: context?.database,
10
+ adapter: context?.adapter,
11
+ emailVerification: {
12
+ sendVerificationEmail: !!options.emailVerification?.sendVerificationEmail,
13
+ sendOnSignUp: !!options.emailVerification?.sendOnSignUp,
14
+ sendOnSignIn: !!options.emailVerification?.sendOnSignIn,
15
+ autoSignInAfterVerification:
16
+ !!options.emailVerification?.autoSignInAfterVerification,
17
+ expiresIn: options.emailVerification?.expiresIn,
18
+ onEmailVerification: !!options.emailVerification?.onEmailVerification,
19
+ afterEmailVerification:
20
+ !!options.emailVerification?.afterEmailVerification,
21
+ },
22
+ emailAndPassword: {
23
+ enabled: !!options.emailAndPassword?.enabled,
24
+ disableSignUp: !!options.emailAndPassword?.disableSignUp,
25
+ requireEmailVerification:
26
+ !!options.emailAndPassword?.requireEmailVerification,
27
+ maxPasswordLength: options.emailAndPassword?.maxPasswordLength,
28
+ minPasswordLength: options.emailAndPassword?.minPasswordLength,
29
+ sendResetPassword: !!options.emailAndPassword?.sendResetPassword,
30
+ resetPasswordTokenExpiresIn:
31
+ options.emailAndPassword?.resetPasswordTokenExpiresIn,
32
+ onPasswordReset: !!options.emailAndPassword?.onPasswordReset,
33
+ password: {
34
+ hash: !!options.emailAndPassword?.password?.hash,
35
+ verify: !!options.emailAndPassword?.password?.verify,
36
+ },
37
+ autoSignIn: !!options.emailAndPassword?.autoSignIn,
38
+ revokeSessionsOnPasswordReset:
39
+ !!options.emailAndPassword?.revokeSessionsOnPasswordReset,
40
+ },
41
+ socialProviders: Object.keys(options.socialProviders || {}).map((p) => {
42
+ const provider =
43
+ options.socialProviders?.[p as keyof typeof options.socialProviders];
44
+ if (!provider) return {};
45
+ return {
46
+ id: p,
47
+ mapProfileToUser: !!provider.mapProfileToUser,
48
+ disableDefaultScope: !!provider.disableDefaultScope,
49
+ disableIdTokenSignIn: !!provider.disableIdTokenSignIn,
50
+ disableImplicitSignUp: provider.disableImplicitSignUp,
51
+ disableSignUp: provider.disableSignUp,
52
+ getUserInfo: !!provider.getUserInfo,
53
+ overrideUserInfoOnSignIn: !!provider.overrideUserInfoOnSignIn,
54
+ prompt: provider.prompt,
55
+ verifyIdToken: !!provider.verifyIdToken,
56
+ scope: provider.scope,
57
+ refreshAccessToken: !!provider.refreshAccessToken,
58
+ };
59
+ }),
60
+ plugins: options.plugins?.map((p) => p.id.toString()),
61
+ user: {
62
+ modelName: options.user?.modelName,
63
+ fields: options.user?.fields,
64
+ additionalFields: options.user?.additionalFields,
65
+ changeEmail: {
66
+ enabled: options.user?.changeEmail?.enabled,
67
+ sendChangeEmailVerification:
68
+ !!options.user?.changeEmail?.sendChangeEmailVerification,
69
+ },
70
+ },
71
+ verification: {
72
+ modelName: options.verification?.modelName,
73
+ disableCleanup: options.verification?.disableCleanup,
74
+ fields: options.verification?.fields,
75
+ },
76
+ session: {
77
+ modelName: options.session?.modelName,
78
+ additionalFields: options.session?.additionalFields,
79
+ cookieCache: {
80
+ enabled: options.session?.cookieCache?.enabled,
81
+ maxAge: options.session?.cookieCache?.maxAge,
82
+ strategy: options.session?.cookieCache?.strategy,
83
+ },
84
+ disableSessionRefresh: options.session?.disableSessionRefresh,
85
+ expiresIn: options.session?.expiresIn,
86
+ fields: options.session?.fields,
87
+ freshAge: options.session?.freshAge,
88
+ preserveSessionInDatabase: options.session?.preserveSessionInDatabase,
89
+ storeSessionInDatabase: options.session?.storeSessionInDatabase,
90
+ updateAge: options.session?.updateAge,
91
+ },
92
+ account: {
93
+ modelName: options.account?.modelName,
94
+ fields: options.account?.fields,
95
+ encryptOAuthTokens: options.account?.encryptOAuthTokens,
96
+ updateAccountOnSignIn: options.account?.updateAccountOnSignIn,
97
+ accountLinking: {
98
+ enabled: options.account?.accountLinking?.enabled,
99
+ trustedProviders: options.account?.accountLinking?.trustedProviders,
100
+ updateUserInfoOnLink:
101
+ options.account?.accountLinking?.updateUserInfoOnLink,
102
+ allowUnlinkingAll: options.account?.accountLinking?.allowUnlinkingAll,
103
+ },
104
+ },
105
+ hooks: {
106
+ after: !!options.hooks?.after,
107
+ before: !!options.hooks?.before,
108
+ },
109
+ secondaryStorage: !!options.secondaryStorage,
110
+ advanced: {
111
+ cookiePrefix: !!options.advanced?.cookiePrefix, //this shouldn't be tracked
112
+ cookies: !!options.advanced?.cookies,
113
+ crossSubDomainCookies: {
114
+ domain: !!options.advanced?.crossSubDomainCookies?.domain,
115
+ enabled: options.advanced?.crossSubDomainCookies?.enabled,
116
+ additionalCookies:
117
+ options.advanced?.crossSubDomainCookies?.additionalCookies,
118
+ },
119
+ database: {
120
+ useNumberId:
121
+ !!options.advanced?.database?.useNumberId ||
122
+ options.advanced?.database?.generateId === "serial",
123
+ generateId: options.advanced?.database?.generateId,
124
+ defaultFindManyLimit: options.advanced?.database?.defaultFindManyLimit,
125
+ },
126
+ useSecureCookies: options.advanced?.useSecureCookies,
127
+ ipAddress: {
128
+ disableIpTracking: options.advanced?.ipAddress?.disableIpTracking,
129
+ ipAddressHeaders: options.advanced?.ipAddress?.ipAddressHeaders,
130
+ },
131
+ disableCSRFCheck: options.advanced?.disableCSRFCheck,
132
+ cookieAttributes: {
133
+ expires: options.advanced?.defaultCookieAttributes?.expires,
134
+ secure: options.advanced?.defaultCookieAttributes?.secure,
135
+ sameSite: options.advanced?.defaultCookieAttributes?.sameSite,
136
+ domain: !!options.advanced?.defaultCookieAttributes?.domain,
137
+ path: options.advanced?.defaultCookieAttributes?.path,
138
+ httpOnly: options.advanced?.defaultCookieAttributes?.httpOnly,
139
+ },
140
+ },
141
+ trustedOrigins: options.trustedOrigins?.length,
142
+ rateLimit: {
143
+ storage: options.rateLimit?.storage,
144
+ modelName: options.rateLimit?.modelName,
145
+ window: options.rateLimit?.window,
146
+ customStorage: !!options.rateLimit?.customStorage,
147
+ enabled: options.rateLimit?.enabled,
148
+ max: options.rateLimit?.max,
149
+ },
150
+ onAPIError: {
151
+ errorURL: options.onAPIError?.errorURL,
152
+ onError: !!options.onAPIError?.onError,
153
+ throw: options.onAPIError?.throw,
154
+ },
155
+ logger: {
156
+ disabled: options.logger?.disabled,
157
+ level: options.logger?.level,
158
+ log: !!options.logger?.log,
159
+ },
160
+ databaseHooks: {
161
+ user: {
162
+ create: {
163
+ after: !!options.databaseHooks?.user?.create?.after,
164
+ before: !!options.databaseHooks?.user?.create?.before,
165
+ },
166
+ update: {
167
+ after: !!options.databaseHooks?.user?.update?.after,
168
+ before: !!options.databaseHooks?.user?.update?.before,
169
+ },
170
+ },
171
+ session: {
172
+ create: {
173
+ after: !!options.databaseHooks?.session?.create?.after,
174
+ before: !!options.databaseHooks?.session?.create?.before,
175
+ },
176
+ update: {
177
+ after: !!options.databaseHooks?.session?.update?.after,
178
+ before: !!options.databaseHooks?.session?.update?.before,
179
+ },
180
+ },
181
+ account: {
182
+ create: {
183
+ after: !!options.databaseHooks?.account?.create?.after,
184
+ before: !!options.databaseHooks?.account?.create?.before,
185
+ },
186
+ update: {
187
+ after: !!options.databaseHooks?.account?.update?.after,
188
+ before: !!options.databaseHooks?.account?.update?.before,
189
+ },
190
+ },
191
+ verification: {
192
+ create: {
193
+ after: !!options.databaseHooks?.verification?.create?.after,
194
+ before: !!options.databaseHooks?.verification?.create?.before,
195
+ },
196
+ update: {
197
+ after: !!options.databaseHooks?.verification?.update?.after,
198
+ before: !!options.databaseHooks?.verification?.update?.before,
199
+ },
200
+ },
201
+ },
202
+ };
203
+ }
@@ -0,0 +1,22 @@
1
+ import type { DetectionInfo } from "../types";
2
+ import { getPackageVersion } from "../utils/package-json";
3
+
4
+ const DATABASES: Record<string, string> = {
5
+ pg: "postgresql",
6
+ mysql: "mysql",
7
+ mariadb: "mariadb",
8
+ sqlite3: "sqlite",
9
+ "better-sqlite3": "sqlite",
10
+ "@prisma/client": "prisma",
11
+ mongoose: "mongodb",
12
+ mongodb: "mongodb",
13
+ "drizzle-orm": "drizzle",
14
+ };
15
+
16
+ export async function detectDatabase(): Promise<DetectionInfo | undefined> {
17
+ for (const [pkg, name] of Object.entries(DATABASES)) {
18
+ const version = await getPackageVersion(pkg);
19
+ if (version) return { name, version };
20
+ }
21
+ return undefined;
22
+ }
@@ -0,0 +1,23 @@
1
+ import { getPackageVersion } from "../utils/package-json";
2
+
3
+ const FRAMEWORKS: Record<string, string> = {
4
+ next: "next",
5
+ nuxt: "nuxt",
6
+ "@remix-run/server-runtime": "remix",
7
+ astro: "astro",
8
+ "@sveltejs/kit": "sveltekit",
9
+ "solid-start": "solid-start",
10
+ "tanstack-start": "tanstack-start",
11
+ hono: "hono",
12
+ express: "express",
13
+ elysia: "elysia",
14
+ expo: "expo",
15
+ };
16
+
17
+ export async function detectFramework() {
18
+ for (const [pkg, name] of Object.entries(FRAMEWORKS)) {
19
+ const version = await getPackageVersion(pkg);
20
+ if (version) return { name, version };
21
+ }
22
+ return undefined;
23
+ }
@@ -0,0 +1,18 @@
1
+ // https://github.com/zkochan/packages/blob/main/which-pm-runs/index.js
2
+ import { env } from "@better-auth/core/env";
3
+
4
+ export function detectPackageManager() {
5
+ const userAgent = env.npm_config_user_agent;
6
+ if (!userAgent) {
7
+ return undefined;
8
+ }
9
+
10
+ const pmSpec = userAgent.split(" ")[0]!;
11
+ const separatorPos = pmSpec.lastIndexOf("/");
12
+ const name = pmSpec.substring(0, separatorPos);
13
+
14
+ return {
15
+ name: name === "npminstall" ? "cnpm" : name,
16
+ version: pmSpec.substring(separatorPos + 1),
17
+ };
18
+ }
@@ -0,0 +1,31 @@
1
+ import { getEnvVar, isTest } from "@better-auth/core/env";
2
+ import { isCI } from "./detect-system-info";
3
+
4
+ export function detectRuntime() {
5
+ // @ts-expect-error: TS doesn't know about Deno global
6
+ if (typeof Deno !== "undefined") {
7
+ // @ts-expect-error: TS doesn't know about Deno global
8
+ const denoVersion = Deno?.version?.deno ?? null;
9
+ return { name: "deno", version: denoVersion };
10
+ }
11
+
12
+ if (typeof Bun !== "undefined") {
13
+ const bunVersion = Bun?.version ?? null;
14
+ return { name: "bun", version: bunVersion };
15
+ }
16
+
17
+ if (typeof process !== "undefined" && process?.versions?.node) {
18
+ return { name: "node", version: process.versions.node ?? null };
19
+ }
20
+ return { name: "edge", version: null };
21
+ }
22
+
23
+ export function detectEnvironment() {
24
+ return getEnvVar("NODE_ENV") === "production"
25
+ ? "production"
26
+ : isCI()
27
+ ? "ci"
28
+ : isTest()
29
+ ? "test"
30
+ : "development";
31
+ }
@@ -0,0 +1,209 @@
1
+ import { env } from "@better-auth/core/env";
2
+ import { importRuntime } from "../utils/import-util";
3
+
4
+ function getVendor() {
5
+ const hasAny = (...keys: string[]) =>
6
+ keys.some((k) => Boolean((env as any)[k]));
7
+
8
+ if (
9
+ hasAny("CF_PAGES", "CF_PAGES_URL", "CF_ACCOUNT_ID") ||
10
+ (typeof navigator !== "undefined" &&
11
+ navigator.userAgent === "Cloudflare-Workers")
12
+ ) {
13
+ return "cloudflare";
14
+ }
15
+
16
+ if (hasAny("VERCEL", "VERCEL_URL", "VERCEL_ENV")) return "vercel";
17
+
18
+ if (hasAny("NETLIFY", "NETLIFY_URL")) return "netlify";
19
+
20
+ if (
21
+ hasAny(
22
+ "RENDER",
23
+ "RENDER_URL",
24
+ "RENDER_INTERNAL_HOSTNAME",
25
+ "RENDER_SERVICE_ID",
26
+ )
27
+ ) {
28
+ return "render";
29
+ }
30
+
31
+ if (
32
+ hasAny("AWS_LAMBDA_FUNCTION_NAME", "AWS_EXECUTION_ENV", "LAMBDA_TASK_ROOT")
33
+ ) {
34
+ return "aws";
35
+ }
36
+
37
+ if (
38
+ hasAny(
39
+ "GOOGLE_CLOUD_FUNCTION_NAME",
40
+ "GOOGLE_CLOUD_PROJECT",
41
+ "GCP_PROJECT",
42
+ "K_SERVICE",
43
+ )
44
+ ) {
45
+ return "gcp";
46
+ }
47
+
48
+ if (
49
+ hasAny(
50
+ "AZURE_FUNCTION_NAME",
51
+ "FUNCTIONS_WORKER_RUNTIME",
52
+ "WEBSITE_INSTANCE_ID",
53
+ "WEBSITE_SITE_NAME",
54
+ )
55
+ ) {
56
+ return "azure";
57
+ }
58
+
59
+ if (hasAny("DENO_DEPLOYMENT_ID", "DENO_REGION")) return "deno-deploy";
60
+
61
+ if (hasAny("FLY_APP_NAME", "FLY_REGION", "FLY_ALLOC_ID")) return "fly-io";
62
+
63
+ if (hasAny("RAILWAY_STATIC_URL", "RAILWAY_ENVIRONMENT_NAME"))
64
+ return "railway";
65
+
66
+ if (hasAny("DYNO", "HEROKU_APP_NAME")) return "heroku";
67
+
68
+ if (hasAny("DO_DEPLOYMENT_ID", "DO_APP_NAME", "DIGITALOCEAN"))
69
+ return "digitalocean";
70
+
71
+ if (hasAny("KOYEB", "KOYEB_DEPLOYMENT_ID", "KOYEB_APP_NAME")) return "koyeb";
72
+
73
+ return null;
74
+ }
75
+
76
+ export async function detectSystemInfo() {
77
+ try {
78
+ //check if it's cloudflare
79
+ if (getVendor() === "cloudflare") return "cloudflare";
80
+ const os = await importRuntime<typeof import("os")>("os");
81
+ const cpus = os.cpus();
82
+ return {
83
+ deploymentVendor: getVendor(),
84
+ systemPlatform: os.platform(),
85
+ systemRelease: os.release(),
86
+ systemArchitecture: os.arch(),
87
+ cpuCount: cpus.length,
88
+ cpuModel: cpus.length ? cpus[0]!.model : null,
89
+ cpuSpeed: cpus.length ? cpus[0]!.speed : null,
90
+ memory: os.totalmem(),
91
+ isWSL: await isWsl(),
92
+ isDocker: await isDocker(),
93
+ isTTY:
94
+ typeof process !== "undefined" && (process as any).stdout
95
+ ? (process as any).stdout.isTTY
96
+ : null,
97
+ };
98
+ } catch {
99
+ return {
100
+ systemPlatform: null,
101
+ systemRelease: null,
102
+ systemArchitecture: null,
103
+ cpuCount: null,
104
+ cpuModel: null,
105
+ cpuSpeed: null,
106
+ memory: null,
107
+ isWSL: null,
108
+ isDocker: null,
109
+ isTTY: null,
110
+ };
111
+ }
112
+ }
113
+
114
+ let isDockerCached: boolean | undefined;
115
+
116
+ async function hasDockerEnv() {
117
+ if (getVendor() === "cloudflare") return false;
118
+
119
+ try {
120
+ const fs = await importRuntime<typeof import("fs")>("fs");
121
+ fs.statSync("/.dockerenv");
122
+ return true;
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+
128
+ async function hasDockerCGroup() {
129
+ if (getVendor() === "cloudflare") return false;
130
+ try {
131
+ const fs = await importRuntime<typeof import("fs")>("fs");
132
+ return fs.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+
138
+ async function isDocker() {
139
+ if (getVendor() === "cloudflare") return false;
140
+
141
+ if (isDockerCached === undefined) {
142
+ isDockerCached = (await hasDockerEnv()) || (await hasDockerCGroup());
143
+ }
144
+
145
+ return isDockerCached;
146
+ }
147
+
148
+ async function isWsl() {
149
+ try {
150
+ if (getVendor() === "cloudflare") return false;
151
+ if (typeof process === "undefined" || process?.platform !== "linux") {
152
+ return false;
153
+ }
154
+ const fs = await importRuntime<typeof import("fs")>("fs");
155
+ const os = await importRuntime<typeof import("os")>("os");
156
+ if (os.release().toLowerCase().includes("microsoft")) {
157
+ if (await isInsideContainer()) {
158
+ return false;
159
+ }
160
+
161
+ return true;
162
+ }
163
+
164
+ return fs
165
+ .readFileSync("/proc/version", "utf8")
166
+ .toLowerCase()
167
+ .includes("microsoft")
168
+ ? !(await isInsideContainer())
169
+ : false;
170
+ } catch {
171
+ return false;
172
+ }
173
+ }
174
+
175
+ let isInsideContainerCached: boolean | undefined;
176
+
177
+ const hasContainerEnv = async () => {
178
+ if (getVendor() === "cloudflare") return false;
179
+ try {
180
+ const fs = await importRuntime<typeof import("fs")>("fs");
181
+ fs.statSync("/run/.containerenv");
182
+ return true;
183
+ } catch {
184
+ return false;
185
+ }
186
+ };
187
+
188
+ async function isInsideContainer() {
189
+ if (isInsideContainerCached === undefined) {
190
+ isInsideContainerCached = (await hasContainerEnv()) || (await isDocker());
191
+ }
192
+
193
+ return isInsideContainerCached;
194
+ }
195
+
196
+ export function isCI() {
197
+ return (
198
+ env.CI !== "false" &&
199
+ ("BUILD_ID" in env || // Jenkins, Cloudbees
200
+ "BUILD_NUMBER" in env || // Jenkins, TeamCity (fixed typo: extra space removed)
201
+ "CI" in env || // Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari, Cloudflare
202
+ "CI_APP_ID" in env || // Appflow
203
+ "CI_BUILD_ID" in env || // Appflow
204
+ "CI_BUILD_NUMBER" in env || // Appflow
205
+ "CI_NAME" in env || // Codeship and others
206
+ "CONTINUOUS_INTEGRATION" in env || // Travis CI, Cirrus CI
207
+ "RUN_ID" in env) // TaskCluster, dsari
208
+ );
209
+ }
package/src/index.ts ADDED
@@ -0,0 +1,90 @@
1
+ import type { BetterAuthOptions } from "@better-auth/core";
2
+ import { ENV, getBooleanEnvVar, isTest, logger } from "@better-auth/core/env";
3
+ import { betterFetch } from "@better-fetch/fetch";
4
+ import { getTelemetryAuthConfig } from "./detectors/detect-auth-config";
5
+ import { detectDatabase } from "./detectors/detect-database";
6
+ import { detectFramework } from "./detectors/detect-framework";
7
+ import { detectPackageManager } from "./detectors/detect-project-info";
8
+ import { detectEnvironment, detectRuntime } from "./detectors/detect-runtime";
9
+ import { detectSystemInfo } from "./detectors/detect-system-info";
10
+ import { getProjectId } from "./project-id";
11
+ import type { TelemetryContext, TelemetryEvent } from "./types";
12
+ export { getTelemetryAuthConfig };
13
+ export type { TelemetryEvent } from "./types";
14
+
15
+ const noop: (event: TelemetryEvent) => Promise<void> = async function noop() {};
16
+
17
+ export async function createTelemetry(
18
+ options: BetterAuthOptions,
19
+ context?: TelemetryContext | undefined,
20
+ ) {
21
+ const debugEnabled =
22
+ options.telemetry?.debug ||
23
+ getBooleanEnvVar("BETTER_AUTH_TELEMETRY_DEBUG", false);
24
+
25
+ const telemetryEndpoint = ENV.BETTER_AUTH_TELEMETRY_ENDPOINT;
26
+ // Return noop if no endpoint and no custom track function
27
+ if (!telemetryEndpoint && !context?.customTrack) {
28
+ return {
29
+ publish: noop,
30
+ };
31
+ }
32
+ const track = async (event: TelemetryEvent) => {
33
+ if (context?.customTrack) {
34
+ await context.customTrack(event).catch(logger.error);
35
+ } else if (telemetryEndpoint) {
36
+ if (debugEnabled) {
37
+ logger.info("telemetry event", JSON.stringify(event, null, 2));
38
+ } else {
39
+ await betterFetch(telemetryEndpoint, {
40
+ method: "POST",
41
+ body: event,
42
+ }).catch(logger.error);
43
+ }
44
+ }
45
+ };
46
+
47
+ const isEnabled = async () => {
48
+ const telemetryEnabled =
49
+ options.telemetry?.enabled !== undefined
50
+ ? options.telemetry.enabled
51
+ : false;
52
+ const envEnabled = getBooleanEnvVar("BETTER_AUTH_TELEMETRY", false);
53
+ return (
54
+ (envEnabled || telemetryEnabled) && (context?.skipTestCheck || !isTest())
55
+ );
56
+ };
57
+
58
+ const enabled = await isEnabled();
59
+ let anonymousId: string | undefined;
60
+
61
+ if (enabled) {
62
+ anonymousId = await getProjectId(options.baseURL);
63
+
64
+ const payload = {
65
+ config: getTelemetryAuthConfig(options, context),
66
+ runtime: detectRuntime(),
67
+ database: await detectDatabase(),
68
+ framework: await detectFramework(),
69
+ environment: detectEnvironment(),
70
+ systemInfo: await detectSystemInfo(),
71
+ packageManager: detectPackageManager(),
72
+ };
73
+
74
+ void track({ type: "init", payload, anonymousId });
75
+ }
76
+
77
+ return {
78
+ publish: async (event: TelemetryEvent) => {
79
+ if (!enabled) return;
80
+ if (!anonymousId) {
81
+ anonymousId = await getProjectId(options.baseURL);
82
+ }
83
+ await track({
84
+ type: event.type,
85
+ payload: event.payload,
86
+ anonymousId,
87
+ });
88
+ },
89
+ };
90
+ }
@@ -0,0 +1,27 @@
1
+ import { hashToBase64 } from "./utils/hash";
2
+ import { generateId } from "./utils/id";
3
+ import { getNameFromLocalPackageJson } from "./utils/package-json";
4
+
5
+ let projectIdCached: string | null = null;
6
+
7
+ export async function getProjectId(
8
+ baseUrl: string | undefined,
9
+ ): Promise<string> {
10
+ if (projectIdCached) return projectIdCached;
11
+
12
+ const projectName = await getNameFromLocalPackageJson();
13
+ if (projectName) {
14
+ projectIdCached = await hashToBase64(
15
+ baseUrl ? baseUrl + projectName : projectName,
16
+ );
17
+ return projectIdCached;
18
+ }
19
+
20
+ if (baseUrl) {
21
+ projectIdCached = await hashToBase64(baseUrl);
22
+ return projectIdCached;
23
+ }
24
+
25
+ projectIdCached = generateId(32);
26
+ return projectIdCached;
27
+ }