Added telemetry to the CLI (#242)

* Basic CLI telemetry

* Telemetry for init command

* Better error handling

* Fix for “Module not found: Can't resolve 'encoding' in” node-fetch warning

* Telemetry for dev and tidied up init

* Minor improvements to the telemetry

* Latest package lock file

* Set the PostHog key to the prod one

* Changesets

* Formatted
This commit is contained in:
Matt Aitken
2023-08-02 09:47:08 +01:00
committed by GitHub
parent 9544624e00
commit facae926ce
12 changed files with 386 additions and 39 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
Fix for a console warning about "encoding" with node-fetch
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/cli": patch
---
Added basic telemetry
+20 -1
View File
@@ -1,5 +1,6 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { prisma } from "~/db.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
export async function loader({ request }: LoaderArgs) {
@@ -10,5 +11,23 @@ export async function loader({ request }: LoaderArgs) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}
return json(authenticationResult.environment);
const environmentWithUser = await prisma.runtimeEnvironment.findUnique({
select: {
orgMember: {
select: {
userId: true,
},
},
},
where: {
id: authenticationResult.environment.id,
},
});
const result = {
...authenticationResult.environment,
userId: environmentWithUser?.orgMember?.userId,
};
return json(result);
}
+2
View File
@@ -62,11 +62,13 @@
"gradient-string": "^2.0.2",
"inquirer": "^9.1.4",
"localtunnel": "^2.0.2",
"nanoid": "^4.0.2",
"ngrok": "5.0.0-beta.2",
"node-fetch": "^3.3.0",
"openai": "^3.3.0",
"ora": "^6.1.2",
"path-to-regexp": "^6.2.1",
"posthog-node": "^3.1.1",
"simple-git": "^3.19.0",
"terminal-link": "^3.0.0",
"tsconfck": "^2.1.2",
+17 -6
View File
@@ -1,11 +1,12 @@
import { Command } from "commander";
import inquirer from "inquirer";
import { initCommand } from "../commands/init.js";
import { COMMAND_NAME, CLOUD_TRIGGER_URL } from "../consts.js";
import { getVersion } from "../utils/getVersion.js";
import pathModule from "node:path";
import { devCommand } from "../commands/dev.js";
import { createIntegrationCommand } from "../commands/createIntegration.js";
import { devCommand } from "../commands/dev.js";
import { initCommand } from "../commands/init.js";
import { CLOUD_TRIGGER_URL, COMMAND_NAME } from "../consts.js";
import { TelemetryClient, telemetryClient } from "../telemetry/telemetry.js";
import { getVersion } from "../utils/getVersion.js";
export const program = new Command();
@@ -27,7 +28,12 @@ program
)
.version(getVersion(), "-v, --version", "Display the version number")
.action(async (options) => {
await initCommand(options);
try {
await initCommand(options);
} catch (e) {
telemetryClient.init.failed("unknown", options, e);
throw e;
}
});
program
@@ -47,7 +53,12 @@ program
)
.version(getVersion(), "-v, --version", "Display the version number")
.action(async (path, options) => {
await devCommand(path, options);
try {
await devCommand(path, options);
} catch (e) {
telemetryClient.dev.failed("unknown", options, e);
throw e;
}
});
program
+68 -11
View File
@@ -10,6 +10,7 @@ import { resolvePath } from "../utils/parseNameAndPath.js";
import { TriggerApi } from "../utils/triggerApi.js";
import dotenv from "dotenv";
import fetch from "node-fetch";
import { TelemetryClient, telemetryClient } from "../telemetry/telemetry.js";
export const DevCommandOptionsSchema = z.object({
port: z.coerce.number(),
@@ -18,7 +19,7 @@ export const DevCommandOptionsSchema = z.object({
clientId: z.string().optional(),
});
type DevCommandOptions = z.infer<typeof DevCommandOptionsSchema>;
export type DevCommandOptions = z.infer<typeof DevCommandOptionsSchema>;
const throttleTimeMs = 1000;
@@ -29,10 +30,13 @@ const formattedDate = new Intl.DateTimeFormat("en", {
});
export async function devCommand(path: string, anyOptions: any) {
telemetryClient.dev.started(path, anyOptions);
const result = DevCommandOptionsSchema.safeParse(anyOptions);
if (!result.success) {
logger.error(result.error.message);
process.exit(1);
telemetryClient.dev.failed("invalid_options", anyOptions, result.error);
return;
}
const options = result.data;
@@ -44,12 +48,20 @@ export async function devCommand(path: string, anyOptions: any) {
logger.error(
"You must run the `init` command first to setup the project you are missing \n'trigger.dev': { 'endpointId': 'your-client-id' } from your package.json file, or pass in the --client-id option to this command"
);
process.exit(1);
telemetryClient.dev.failed("missing_endpoint_id", options);
return;
}
logger.success(`✔️ [trigger.dev] Detected TriggerClient id: ${endpointId}`);
// Read from .env.local or .env to get the TRIGGER_API_KEY and TRIGGER_API_URL
const { apiUrl, envFile, apiKey } = await getTriggerApiDetails(resolvedPath, options.envFile);
const apiDetails = await getTriggerApiDetails(resolvedPath, options.envFile);
if (!apiDetails) {
telemetryClient.dev.failed("missing_api_key", options);
return;
}
const { apiUrl, envFile, apiKey } = apiDetails;
logger.success(`✔️ [trigger.dev] Found API Key in ${envFile} file`);
@@ -68,16 +80,26 @@ export async function devCommand(path: string, anyOptions: any) {
});
} catch (err) {
logger.error(`❌ [trigger.dev] No server found on port ${options.port}.`);
process.exit(1);
telemetryClient.dev.failed("no_server_found", options);
return;
}
telemetryClient.dev.serverRunning(path, options);
// Setup tunnel
const endpointUrl = await resolveEndpointUrl(apiUrl, options.port);
if (!endpointUrl) {
telemetryClient.dev.failed("failed_to_create_tunnel", options);
return;
}
const endpointHandlerUrl = `${endpointUrl}${options.handlerPath}`;
telemetryClient.dev.tunnelRunning(path, options);
const connectingSpinner = ora(`[trigger.dev] Registering endpoint ${endpointHandlerUrl}...`);
//refresh function
let hasConnected = false;
let attemptCount = 0;
const refresh = async () => {
connectingSpinner.start();
@@ -85,10 +107,34 @@ export async function devCommand(path: string, anyOptions: any) {
const refreshedEndpointId = await getEndpointIdFromPackageJson(resolvedPath, options);
// Read from .env.local to get the TRIGGER_API_KEY and TRIGGER_API_URL
const { apiKey, apiUrl } = await getTriggerApiDetails(resolvedPath, envFile);
const apiDetails = await getTriggerApiDetails(resolvedPath, envFile);
if (!apiDetails) {
connectingSpinner.fail(`❌ [trigger.dev] Failed to connect: Missing API Key`);
logger.info(`Will attempt again on the next file change…`);
attemptCount = 0;
return;
}
const { apiKey, apiUrl } = apiDetails;
const apiClient = new TriggerApi(apiKey, apiUrl);
const authorizedKey = await apiClient.whoami(apiKey);
if (!authorizedKey) {
logger.error(
`🛑 The API key you provided is not authorized. Try visiting your dashboard to get a new API key.`
);
telemetryClient.dev.failed("invalid_api_key", options);
return;
}
telemetryClient.identify(
authorizedKey.organization.id,
authorizedKey.project.id,
authorizedKey.userId
);
const result = await refreshEndpoint(
apiClient,
refreshedEndpointId ?? endpointId,
@@ -101,6 +147,11 @@ export async function devCommand(path: string, anyOptions: any) {
new Date(result.data.updatedAt)
)}`
);
if (!hasConnected) {
hasConnected = true;
telemetryClient.dev.connected(path, options);
}
} else {
attemptCount++;
@@ -108,6 +159,10 @@ export async function devCommand(path: string, anyOptions: any) {
connectingSpinner.fail(`🚨 Failed to connect: ${result.error}`);
logger.info(`Will attempt again on the next file change…`);
attemptCount = 0;
if (!hasConnected) {
telemetryClient.dev.failed("failed_to_connect", options);
}
return;
}
@@ -196,14 +251,14 @@ async function getTriggerApiDetails(path: string, envFile: string) {
if (!resolvedEnvFile) {
logger.error(`You must add TRIGGER_API_KEY and TRIGGER_API_URL to your ${envFile} file.`);
process.exit(1);
return;
}
const parsedEnvFile = dotenv.parse(resolvedEnvFile.content);
if (!parsedEnvFile.TRIGGER_API_KEY || !parsedEnvFile.TRIGGER_API_KEY) {
logger.error(`You must add TRIGGER_API_KEY and TRIGGER_API_URL to your ${envFile} file.`);
process.exit(1);
return;
}
const apiKey = parsedEnvFile.TRIGGER_API_KEY;
@@ -211,7 +266,7 @@ async function getTriggerApiDetails(path: string, envFile: string) {
if (!apiKey || !apiUrl) {
logger.error(`You must add TRIGGER_API_KEY and TRIGGER_API_URL to your ${envFile} file.`);
process.exit(1);
return;
}
return { apiKey, apiUrl, envFile: resolvedEnvFile.fileName };
@@ -227,7 +282,9 @@ async function resolveEndpointUrl(apiUrl: string, port: number) {
// Setup tunnel
const tunnelSpinner = ora(`🚇 Creating tunnel`).start();
const tunnelUrl = await createTunnel(port);
tunnelSpinner.succeed(`🚇 Created tunnel: ${tunnelUrl}`);
if (tunnelUrl) {
tunnelSpinner.succeed(`🚇 Created tunnel: ${tunnelUrl}`);
}
return tunnelUrl;
}
@@ -237,7 +294,7 @@ async function createTunnel(port: number) {
return await ngrok.connect(port);
} catch (e) {
logger.error(`Ngrok failed to create a tunnel for port ${port}.\n${e}`);
process.exit(1);
return;
}
}
+41 -17
View File
@@ -5,18 +5,19 @@ import inquirer from "inquirer";
import pathModule from "path";
import { pathToRegexp } from "path-to-regexp";
import { simpleGit } from "simple-git";
import { parse } from "tsconfck";
import { pathToFileURL } from "url";
import { promptApiKey, promptEndpointSlug, promptTriggerUrl } from "../cli/index.js";
import { COMMAND_NAME, CLOUD_TRIGGER_URL, CLOUD_API_URL } from "../consts.js";
import { CLOUD_API_URL, CLOUD_TRIGGER_URL, COMMAND_NAME } from "../consts.js";
import { TelemetryClient, telemetryClient } from "../telemetry/telemetry.js";
import { addDependencies } from "../utils/addDependencies.js";
import { detectNextJsProject } from "../utils/detectNextJsProject.js";
import { pathExists, readJSONFile } from "../utils/fileSystem.js";
import { logger } from "../utils/logger.js";
import { resolvePath } from "../utils/parseNameAndPath.js";
import { renderApiKey } from "../utils/renderApiKey.js";
import { renderTitle } from "../utils/renderTitle.js";
import { detectNextJsProject } from "../utils/detectNextJsProject.js";
import { TriggerApi, WhoamiResponse } from "../utils/triggerApi.js";
import { parse } from "tsconfck";
import { pathExists, readJSONFile } from "../utils/fileSystem.js";
import { pathToFileURL } from "url";
export type InitCommandOptions = {
projectPath: string;
@@ -31,6 +32,8 @@ type ResolvedOptions = Required<InitCommandOptions>;
export const initCommand = async (options: InitCommandOptions) => {
renderTitle();
telemetryClient.init.started(options);
if (options.triggerUrl === CLOUD_TRIGGER_URL) {
logger.info(`✨ Initializing project in Trigger.dev Cloud`);
} else if (typeof options.triggerUrl === "string") {
@@ -45,7 +48,8 @@ export const initCommand = async (options: InitCommandOptions) => {
if (!isNextJsProject) {
logger.error("You must run this command in a Next.js project.");
process.exit(1);
telemetryClient.init.failed("not_nextjs_project", options);
return;
} else {
logger.success("✅ Detected Next.js project");
}
@@ -60,14 +64,15 @@ export const initCommand = async (options: InitCommandOptions) => {
}
const isTypescriptProject = await detectTypescriptProject(resolvedPath);
telemetryClient.init.isTypescriptProject(isTypescriptProject, options);
const resolvedOptions = await resolveOptionsWithPrompts(options, resolvedPath);
const resolvedOptions = await resolveOptionsWithPrompts(options, resolvedPath, telemetryClient);
const apiKey = resolvedOptions.apiKey;
if (!apiKey) {
logger.error("You must provide an API key to continue.");
process.exit(1);
telemetryClient.init.failed("no_api_key", resolvedOptions);
return;
}
const apiClient = new TriggerApi(apiKey, resolvedOptions.apiUrl);
@@ -78,14 +83,23 @@ export const initCommand = async (options: InitCommandOptions) => {
`🛑 The API key you provided is not authorized. Try visiting your dashboard at ${resolvedOptions.triggerUrl} to get a new API key.`
);
process.exit(1);
telemetryClient.init.failed("invalid_api_key", resolvedOptions);
return;
}
telemetryClient.identify(
authorizedKey.organization.id,
authorizedKey.project.id,
authorizedKey.userId
);
await addDependencies(resolvedPath, [
{ name: "@trigger.dev/sdk", tag: "latest" },
{ name: "@trigger.dev/nextjs", tag: "latest" },
]);
telemetryClient.init.addedDependencies(resolvedOptions);
// Setup environment variables
await setupEnvironmentVariables(resolvedPath, resolvedOptions);
@@ -100,6 +114,7 @@ export const initCommand = async (options: InitCommandOptions) => {
const routeDir = pathModule.join(resolvedPath, usesSrcDir ? "src" : "");
if (nextJsDir === "pages") {
telemetryClient.init.createFiles(resolvedOptions, "pages");
await createTriggerPageRoute(
resolvedPath,
routeDir,
@@ -108,6 +123,7 @@ export const initCommand = async (options: InitCommandOptions) => {
usesSrcDir
);
} else {
telemetryClient.init.createFiles(resolvedOptions, "app");
await createTriggerAppRoute(
resolvedPath,
routeDir,
@@ -122,8 +138,7 @@ export const initCommand = async (options: InitCommandOptions) => {
await addConfigurationToPackageJson(resolvedPath, resolvedOptions);
await printNextSteps(resolvedOptions, authorizedKey);
process.exit(0);
telemetryClient.init.completed(resolvedOptions);
};
async function printNextSteps(options: ResolvedOptions, authorizedKey: WhoamiResponse) {
@@ -160,7 +175,8 @@ async function addConfigurationToPackageJson(path: string, options: ResolvedOpti
const resolveOptionsWithPrompts = async (
options: InitCommandOptions,
path: string
path: string,
telemetryClient: TelemetryClient
): Promise<ResolvedOptions> => {
const resolvedOptions: InitCommandOptions = { ...options };
@@ -175,21 +191,24 @@ const resolveOptionsWithPrompts = async (
resolvedOptions.apiUrl = resolvedOptions.triggerUrl;
}
telemetryClient.init.resolvedApiUrl(resolvedOptions.apiUrl, resolvedOptions);
if (!options.apiKey) {
resolvedOptions.apiKey = await promptApiKey(resolvedOptions.triggerUrl!);
}
telemetryClient.init.resolvedApiKey(resolvedOptions);
if (!options.endpointSlug) {
const packageJSONPath = pathModule.join(path, "package.json");
const packageJSON = await readJSONFile(packageJSONPath);
if (packageJSON && packageJSON["trigger.dev"] && packageJSON["trigger.dev"].endpointId) {
options.endpointSlug = packageJSON["trigger.dev"].endpointId;
resolvedOptions.endpointSlug = packageJSON["trigger.dev"].endpointId;
} else {
options.endpointSlug = await promptEndpointSlug(path);
resolvedOptions.endpointSlug = await promptEndpointSlug(path);
}
resolvedOptions.endpointSlug = await promptEndpointSlug(path);
telemetryClient.init.resolvedEndpointSlug(resolvedOptions);
}
} catch (err) {
// If the user is not calling the command from an interactive terminal, inquirer will throw an error with isTTYError = true
@@ -209,10 +228,12 @@ const resolveOptionsWithPrompts = async (
});
if (!shouldContinue) {
telemetryClient.init.failed("non_interactive_terminal", options);
logger.info("Exiting...");
process.exit(0);
throw err;
}
} else {
telemetryClient.init.failed("unknown", options, err);
throw err;
}
}
@@ -299,6 +320,7 @@ async function detectMiddlewareUsage(path: string, usesSrcDir = false) {
)} which can cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
);
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
return;
}
@@ -317,6 +339,7 @@ async function detectMiddlewareUsage(path: string, usesSrcDir = false) {
middlewarePath
)} which will cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
);
telemetryClient.init.warning("middleware_conflict_api_trigger", { projectPath: path });
}
} else if (Array.isArray(matcher) && matcher.every((m) => typeof m === "string")) {
const matcherRegexes = matcher.map((m) => pathToRegexp(m));
@@ -328,6 +351,7 @@ async function detectMiddlewareUsage(path: string, usesSrcDir = false) {
middlewarePath
)} which will cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
);
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
}
}
}
+1 -2
View File
@@ -8,12 +8,11 @@ const main = async () => {
};
main().catch((err) => {
logger.error("Aborting installation...");
if (err instanceof Error) {
logger.error(err);
} else {
logger.error("An unknown error has occurred. Please open an issue on github with the below:");
console.log(err);
logger.error(err);
}
process.exit(1);
});
+183
View File
@@ -0,0 +1,183 @@
import { PostHog } from "posthog-node";
import { InitCommandOptions } from "../commands/init.js";
import { nanoid } from "nanoid";
import { getVersion } from "../utils/getVersion.js";
import { DevCommandOptions } from "../commands/dev.js";
const postHogApiKey = "phc_hwYmedO564b3Ik8nhA4Csrb5SueY0EwFJWCbseGwWW";
export class TelemetryClient {
#client: PostHog;
#sessionId: string;
#version: string;
constructor() {
this.#client = new PostHog(postHogApiKey, {
host: "https://app.posthog.com",
flushAt: 1,
});
this.#sessionId = `cli-${nanoid()}`;
this.#version = getVersion();
}
identify(organizationId: string, projectId: string, userId?: string) {
if (userId) {
this.#client.alias({
distinctId: userId,
alias: this.#sessionId,
});
}
this.#client.groupIdentify({
groupType: "organization",
groupKey: organizationId,
});
this.#client.groupIdentify({
groupType: "project",
groupKey: projectId,
});
}
init = {
started: (options: InitCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_started",
properties: this.#initProperties(options),
});
},
isTypescriptProject: (isTypescriptProject: boolean, options: InitCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_typescriptproject",
properties: { ...this.#initProperties(options), isTypescriptProject },
});
},
resolvedApiUrl: (apiUrl: string | undefined, options: InitCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_resolved_apiurl",
properties: { ...this.#initProperties(options), apiUrl },
});
},
resolvedApiKey: (options: InitCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_resolved_apikey",
properties: this.#initProperties(options),
});
},
resolvedEndpointSlug: (options: InitCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_resolved_endpoint_slug",
properties: this.#initProperties(options),
});
},
addedDependencies: (options: InitCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_added_dependencies",
properties: this.#initProperties(options),
});
},
createFiles: (options: InitCommandOptions, routerType: "pages" | "app") => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_create_files",
properties: { ...this.#initProperties(options), routerType },
});
},
completed: (options: InitCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_completed",
properties: this.#initProperties(options),
});
},
warning: (reason: string, options: InitCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_warning",
properties: {
...this.#initProperties(options),
reason,
},
});
},
failed: (reason: string, options: InitCommandOptions, error?: unknown) => {
const errorString = error instanceof Error ? error.message : String(error);
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_init_failed",
properties: {
...this.#initProperties(options),
reason,
error: errorString,
},
});
},
};
dev = {
started: (path: string, options: Record<string, string | number | boolean>) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_dev_started",
properties: { ...options, path },
});
},
serverRunning: (path: string, options: DevCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_dev_server_running",
properties: { ...options, path },
});
},
tunnelRunning: (path: string, options: DevCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_dev_tunnel_running",
properties: { ...options, path },
});
},
connected: (path: string, options: DevCommandOptions) => {
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_dev_connected",
properties: { ...options, path },
});
},
failed: (
reason: string,
options: Record<string, string | number | boolean>,
error?: unknown
) => {
const errorString = error instanceof Error ? error.message : String(error);
this.#client.capture({
distinctId: this.#sessionId,
event: "cli_dev_failed",
properties: {
...options,
reason,
error: errorString,
},
});
},
};
#initProperties(options: InitCommandOptions) {
return {
version: this.#version,
hadApiKey: options.apiKey !== undefined,
triggerUrl: options.triggerUrl,
endpointSlug: options.endpointSlug,
apiUrl: options.apiUrl,
};
}
}
export const telemetryClient = new TelemetryClient();
+1
View File
@@ -47,6 +47,7 @@ const WhoamiResponseSchema = z.object({
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
}),
userId: z.string().optional(),
});
export type WhoamiResponse = z.infer<typeof WhoamiResponseSchema>;
+1
View File
@@ -49,6 +49,7 @@
"@types/slug": "^5.0.3",
"@types/uuid": "^9.0.0",
"@types/ws": "^8.5.3",
"encoding": "^0.1.13",
"rimraf": "^3.0.2",
"tsup": "^6.5.0",
"tsx": "^3.12.1",
+42 -2
View File
@@ -755,11 +755,13 @@ importers:
gradient-string: ^2.0.2
inquirer: ^9.1.4
localtunnel: ^2.0.2
nanoid: ^4.0.2
ngrok: 5.0.0-beta.2
node-fetch: ^3.3.0
openai: ^3.3.0
ora: ^6.1.2
path-to-regexp: ^6.2.1
posthog-node: ^3.1.1
rimraf: ^3.0.2
simple-git: ^3.19.0
terminal-link: ^3.0.0
@@ -780,11 +782,13 @@ importers:
gradient-string: 2.0.2
inquirer: 9.1.4
localtunnel: 2.0.2
nanoid: 4.0.2
ngrok: 5.0.0-beta.2
node-fetch: 3.3.0
openai: 3.3.0
ora: 6.1.2
path-to-regexp: 6.2.1
posthog-node: 3.1.1
simple-git: 3.19.0
terminal-link: 3.0.0
tsconfck: 2.1.2_typescript@4.9.5
@@ -988,6 +992,7 @@ importers:
chalk: ^5.2.0
cronstrue: ^2.21.0
debug: ^4.3.4
encoding: ^0.1.13
evt: ^2.4.13
get-caller-file: ^2.0.5
git-remote-origin-url: ^4.0.0
@@ -1012,7 +1017,7 @@ importers:
get-caller-file: 2.0.5
git-remote-origin-url: 4.0.0
git-repo-info: 2.1.1
node-fetch: 2.6.7
node-fetch: 2.6.7_encoding@0.1.13
slug: 6.1.0
terminal-link: 3.0.0
ulid: 2.3.0
@@ -1029,6 +1034,7 @@ importers:
'@types/slug': 5.0.3
'@types/uuid': 9.0.0
'@types/ws': 8.5.4
encoding: 0.1.13
rimraf: 3.0.2
tsup: 6.5.0_typescript@4.9.5
tsx: 3.12.2
@@ -11235,7 +11241,7 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
'@typescript-eslint/types': 5.59.6
eslint-visitor-keys: 3.4.1
eslint-visitor-keys: 3.4.2
/@uiw/codemirror-extensions-basic-setup/4.19.5_wd2tsis3in55bkaiwnc2c46tom:
resolution: {integrity: sha512-1zt7ZPJ01xKkSW/KDy0FZNga0bngN1fC594wCVG7FBi60ehfcAucpooQ+JSPScKXopxcb+ugPKZvVLzr9/OfzA==}
@@ -13915,6 +13921,11 @@ packages:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
/encoding/0.1.13:
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
dependencies:
iconv-lite: 0.6.3
/end-of-stream/1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
dependencies:
@@ -15393,6 +15404,10 @@ packages:
resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
/eslint-visitor-keys/3.4.2:
resolution: {integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
/eslint/8.31.0:
resolution: {integrity: sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -17134,6 +17149,12 @@ packages:
dependencies:
safer-buffer: 2.1.2
/iconv-lite/0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
/icss-utils/5.1.0_postcss@8.4.24:
resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
engines: {node: ^10 || ^12 || >= 14}
@@ -19319,6 +19340,12 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
/nanoid/4.0.2:
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
engines: {node: ^14 || ^16 || >=18}
hasBin: true
dev: false
/nanomatch/1.2.13:
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
engines: {node: '>=0.10.0'}
@@ -19668,6 +19695,19 @@ packages:
dependencies:
whatwg-url: 5.0.0
/node-fetch/2.6.7_encoding@0.1.13:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
encoding: 0.1.13
whatwg-url: 5.0.0
dev: false
/node-fetch/3.3.0:
resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}