WIP converting the init into a multi-command CLI to support dev

This commit is contained in:
Eric Allam
2023-06-29 18:15:15 +01:00
parent 5d887f1cfb
commit 29a02131d2
32 changed files with 569 additions and 518 deletions
+6 -3
View File
@@ -20,10 +20,13 @@
"react-dom": "^18.2.0",
"tailwindcss": "3.3.2",
"typescript": "5.1.6",
"@trigger.dev/sdk": "2.0.0-next.4",
"@trigger.dev/nextjs": "1.0.0-next.5"
"@trigger.dev/sdk": "^2.0.0-next.4",
"@trigger.dev/nextjs": "^1.0.0-next.5"
},
"devDependencies": {
"@trigger.dev/init": "workspace:*"
"@trigger.dev/cli": "workspace:*"
},
"trigger.dev": {
"endpointId": "nextjs-clerk"
}
}
@@ -1,10 +1,8 @@
import { Job, TriggerClient, eventTrigger } from "@trigger.dev/sdk";
import { createAppRoute } from "@trigger.dev/nextjs";
const client = new TriggerClient({
id: "my-nextjs-project",
url: process.env.VERCEL_URL,
apiKey: process.env.TRIGGER_API_KEY,
apiUrl: process.env.TRIGGER_API_URL,
});
@@ -25,7 +23,4 @@ new Job(client, {
},
});
export const { POST, dynamic } = createAppRoute(client, {
path: "/api/trigger",
});
export const { POST, dynamic } = createAppRoute(client);
@@ -5,7 +5,6 @@ import { Job, TriggerClient } from "@trigger.dev/sdk";
export const client = new TriggerClient({
id: "nextjs-appdir-example",
url: process.env.VERCEL_URL,
apiKey: process.env.TRIGGER_API_KEY,
apiUrl: process.env.TRIGGER_API_URL,
logLevel: "debug",
@@ -38,6 +37,4 @@ new Job(client, {
},
});
export const { POST, dynamic } = createAppRoute(client, {
path: "/api/v2/trigger",
});
export const { POST, dynamic } = createAppRoute(client);
@@ -4,7 +4,7 @@ import "@/jobs/openai";
import "@/jobs/resend";
import { createPagesRoute } from "@trigger.dev/nextjs";
const { handler, config } = createPagesRoute(client, { path: "/api/trigger" });
const { handler, config } = createPagesRoute(client);
export { config };
-1
View File
@@ -26,7 +26,6 @@ import fetch from "node-fetch";
export const client = new TriggerClient({
id: "nextjs-example",
url: process.env.VERCEL_URL,
apiKey: process.env.TRIGGER_API_KEY,
apiUrl: process.env.TRIGGER_API_URL,
logLevel: "debug",
+19
View File
@@ -0,0 +1,19 @@
## How to run the CLI locally
First, cd into the cli directory:
```sh
cd packages/cli
```
Then run `pnpm run dev` which will build and re-build on any changes to the CLI package
Next, head to an example repo (e.g. `examples/nextjs-clerk`) and add the package to `devDependencies` if it's not already:
```json
"devDependencies": {
"@trigger.dev/cli": "workspace:*"
},
```
After running `pnpm i`, you should be able to cd to the `examples/nextjs-clerk` and then run `pnpm exec cli` to run the `@trigger.dev/cli`. You won't need to keep doing `pnpm i` or anything to pickup changes from `packages/cli`.
@@ -1,14 +1,14 @@
{
"name": "@trigger.dev/init",
"name": "@trigger.dev/cli",
"version": "0.2.1-next.8",
"description": "The CLI to easily initialize Trigger.dev in your Next.js project",
"description": "The Trigger.dev CLI",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/triggerdotdev/trigger.dev.git",
"directory": "packages/init-trigger"
"directory": "packages/cli"
},
"publishConfig": {
"access": "public"
@@ -32,7 +32,7 @@
"type": "module",
"exports": "./dist/index.js",
"bin": {
"init": "./dist/index.js"
"cli": "./dist/index.js"
},
"devDependencies": {
"@types/fs-extra": "^11.0.1",
@@ -61,6 +61,8 @@
"fs-extra": "^11.1.0",
"gradient-string": "^2.0.2",
"inquirer": "^9.1.4",
"localtunnel": "^2.0.2",
"ngrok": "5.0.0-beta.2",
"node-fetch": "^3.3.0",
"ora": "^6.1.2",
"path-to-regexp": "^6.2.1",
+201
View File
@@ -0,0 +1,201 @@
import { Command, Option } from "commander";
import inquirer from "inquirer";
import { initCommand } from "../commands/init.js";
import { COMMAND_NAME, DEFAULT_TRIGGER_URL } from "../consts.js";
import { getVersion } from "../utils/getVersion.js";
import pathModule from "node:path";
import { devCommand } from "../commands/dev.js";
export const program = new Command();
program.name(COMMAND_NAME).description("The Trigger.dev CLI").version("0.0.1");
program
.command("init")
.description("Initialize Trigger.dev in your Next.js project")
.option(
"-p, --project-path <project-path>",
"The path to the Next.js project",
"."
)
.option(
"-k, --api-key <api-key>",
"The development API key to use for the project."
)
.option(
"-e, --endpoint-id <endpoint-id>",
"The unique ID for the endpoint to use for this project. (e.g. my-nextjs-project)"
)
.option(
"-t, --trigger-url <trigger-url>",
"The URL of the Trigger.dev instance to use."
)
.version(getVersion(), "-v, --version", "Display the version number")
.action(async (options) => {
await initCommand(options);
});
program
.command("dev")
.description(
"Tunnel your local Next.js project to Trigger.dev and start running jobs"
)
.argument("[path]", "The path to the Next.js project", ".")
.option(
"-p, --port <port>",
"The local port your Next.js project is on",
"3000"
)
.option(
"-e, --env-file <name>",
"The name of the env file to load",
".env.local"
)
.addOption(
new Option("-t, --tunnel <tunnel>", "Choose the tunnel provider")
.choices(["ngrok", "localtunnel", "trigger.dev (coming soon)"])
.default("ngrok")
)
.version(getVersion(), "-v, --version", "Display the version number")
.action(async (options) => {
await devCommand(options);
});
export const promptTriggerUrl = async (): Promise<string> => {
const { instanceType } = await inquirer.prompt<{
instanceType: "cloud" | "self-hosted";
}>([
{
type: "list",
name: "instanceType",
message: "Are you using the Trigger.dev cloud or self-hosted?",
choices: [
{
name: "Trigger.dev Cloud (https://cloud.trigger.dev)",
value: "cloud",
default: true,
},
{
name: "Self hosted",
value: "self-hosted",
},
],
},
]);
if (instanceType === "cloud") {
return DEFAULT_TRIGGER_URL;
}
const { triggerUrl } = await inquirer.prompt<{ triggerUrl: string }>({
type: "input",
name: "triggerUrl",
message: "Enter the URL of your self-hosted Trigger.dev instance",
validate: (input) => {
if (!input) {
return "Please enter the URL of your self-hosted Trigger.dev instance";
}
return true;
},
});
return triggerUrl;
};
export const promptApiKey = async (instanceUrl: string): Promise<string> => {
// First prompt if they want to enter their API key now, and if they say Yes, then prompt for it and return it
const { apiKey } = await inquirer.prompt<{ apiKey: string }>({
type: "input",
name: "apiKey",
message: `Enter your development API key (Find yours ➡️ ${instanceUrl})`,
validate: (input) => {
// Make sure they enter something like tr_dev_********
if (!input) {
return "Please enter your development API key";
}
if (!input.startsWith("tr_dev_")) {
return "Please enter a valid development API key or leave blank to skip (should start with tr_dev_)";
}
return true;
},
});
return apiKey;
};
export const promptEndpointSlug = async (path: string): Promise<string> => {
const { endpointSlug } = await inquirer.prompt<{
endpointSlug: string;
}>({
type: "input",
name: "endpointSlug",
default: slugify(pathModule.basename(path)),
message: "Enter a unique ID for your endpoint",
validate: (input) => {
if (!input) {
return "Please enter a unique slug for your endpoint";
}
return true;
},
});
return endpointSlug;
};
export const promptEndpointUrl = async (
instanceUrl: string
): Promise<string> => {
const { endpointUrl } = await inquirer.prompt<{
endpointUrl: string;
}>({
type: "input",
name: "endpointUrl",
message: "What's the URL of your Next.js project?",
validate: (input) => {
if (!input) {
return "Please enter the URL of your Next.js project";
}
// If instanceUrl is a cloud instance, then the URL must be publicly accessible
const url = new URL(input);
const triggerUrl = new URL(instanceUrl);
if (triggerUrl.hostname !== "localhost" && url.hostname === "localhost") {
return `Your Trigger.dev instance is hosted at ${triggerUrl.hostname}, so your Next.js project must also be publicly accessible. See our docs for more info: https://trigger.dev/docs/documentation/guides/tunneling-localhost`;
}
// Make sure triggerUrl and url don't use the same port if they are both localhost
if (
triggerUrl.hostname === "localhost" &&
url.hostname === "localhost" &&
triggerUrl.port === url.port
) {
return `Your Trigger.dev instance and your Next.js project are both trying to use port ${triggerUrl.port}. Please use a different port for one of them`;
}
return true;
},
});
return endpointUrl;
};
export const obfuscateApiKey = (apiKey: string) => {
const [prefix, slug, secretPart] = apiKey.split("_") as [
string,
string,
string
];
return `${prefix}_${slug}_${"*".repeat(secretPart.length)}`;
};
function slugify(value: string): string {
return value
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
}
+8
View File
@@ -0,0 +1,8 @@
export type DevCommandOptions = {
projectPath: string;
};
export async function devCommand(options: DevCommandOptions) {
// Read from package.json to get the endpointId
// Read from .env.local to get the TRIGGER_API_KEY and TRIGGER_API_URL
}
@@ -1,35 +1,45 @@
#!/usr/bin/env node
import { CliResults, parseCliOptions, runCliPrompts } from "./cli/index.js";
import { addDependencies } from "./utils/addDependencies.js";
import { logger } from "./utils/logger.js";
import { resolvePath } from "./utils/parseNameAndPath.js";
import { renderTitle } from "./utils/renderTitle.js";
import fs from "fs/promises";
import inquirer from "inquirer";
import pathModule from "path";
import { simpleGit } from "simple-git";
import { TriggerApi } from "./utils/triggerApi.js";
import { DEFAULT_TRIGGER_URL } from "./consts.js";
import ora from "ora";
import { renderApiKey } from "./utils/renderApiKey.js";
import { pathToRegexp } from "path-to-regexp";
import { simpleGit } from "simple-git";
import {
promptApiKey,
promptEndpointSlug,
promptTriggerUrl,
} from "../cli/index.js";
import { COMMAND_NAME, DEFAULT_TRIGGER_URL } from "../consts.js";
import { addDependencies } from "../utils/addDependencies.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";
const main = async () => {
export type InitCommandOptions = {
projectPath: string;
triggerUrl?: string;
endpointSlug?: string;
apiKey?: string;
};
type ResolvedOptions = Required<InitCommandOptions>;
export const initCommand = async (options: InitCommandOptions) => {
renderTitle();
const cliOptions = await parseCliOptions();
if (cliOptions.flags.triggerUrl === DEFAULT_TRIGGER_URL) {
if (options.triggerUrl === DEFAULT_TRIGGER_URL) {
logger.info(`✨ Initializing project in Trigger.dev Cloud`);
} else if (typeof cliOptions.flags.triggerUrl === "string") {
} else if (typeof options.triggerUrl === "string") {
logger.info(
`✨ Initializing project using Trigger.dev at ${cliOptions.flags.triggerUrl}`
`✨ Initializing project using Trigger.dev at ${options.triggerUrl}`
);
} else {
logger.info(`✨ Initializing Trigger.dev in project`);
}
const resolvedPath = resolvePath(cliOptions.flags.projectPath);
const resolvedPath = resolvePath(options.projectPath);
// Detect if are are in a Next.js project
const isNextJsProject = await detectNextJsProject(resolvedPath);
@@ -60,9 +70,12 @@ const main = async () => {
process.exit(1);
}
const cliResults = await runCliPrompts(cliOptions);
const resolvedOptions = await resolveOptionsWithPrompts(
options,
resolvedPath
);
const apiKey = cliResults.flags.apiKey;
const apiKey = resolvedOptions.apiKey;
if (!apiKey) {
logger.error("You must provide an API key to continue.");
@@ -75,7 +88,7 @@ const main = async () => {
]);
// Setup environment variables
await setupEnvironmentVariables(resolvedPath, cliResults);
await setupEnvironmentVariables(resolvedPath, resolvedOptions);
const usesSrcDir = await detectUseOfSrcDir(resolvedPath);
@@ -88,52 +101,94 @@ const main = async () => {
const routeDir = pathModule.join(resolvedPath, usesSrcDir ? "src" : "");
if (nextJsDir === "pages") {
await createTriggerPageRoute(routeDir, cliResults, usesSrcDir);
await createTriggerPageRoute(routeDir, resolvedOptions, usesSrcDir);
} else {
await createTriggerAppRoute(routeDir, cliResults, usesSrcDir);
await createTriggerAppRoute(routeDir, resolvedOptions, usesSrcDir);
}
await detectMiddlewareUsage(resolvedPath, usesSrcDir);
await waitForProjectToBuild();
await addConfigurationToPackageJson(resolvedPath, resolvedOptions);
const api = new TriggerApi(apiKey, cliResults.flags.triggerUrl);
const response = await api.createEndpoint({
id: cliResults.flags.endpointSlug,
url: `${cliResults.flags.endpointUrl}${
cliResults.flags.endpointUrl.endsWith("/") ? "" : "/"
}api/trigger`,
});
if (!response.ok) {
logger.error(
`${response.error}. Please contact eric@trigger.dev for assistance.`
);
process.exit(1);
}
logger.success(`✅ Successfully initialized Trigger.dev!`);
logger.info(
`🔗 Visit your Trigger.dev dashboard at ${cliResults.flags.triggerUrl}`
);
await printNextSteps(resolvedOptions);
process.exit(0);
};
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);
async function printNextSteps(options: ResolvedOptions) {
logger.success(`✅ Successfully initialized Trigger.dev!`);
logger.info(
`🔗 You can now connect to ${options.triggerUrl} and run jobs locally using the '@trigger.dev/cli dev' command`
);
}
async function addConfigurationToPackageJson(
path: string,
options: ResolvedOptions
) {
const pkgJsonPath = pathModule.join(path, "package.json");
const pkgBuffer = await fs.readFile(pkgJsonPath);
const pkgJson = JSON.parse(pkgBuffer.toString());
pkgJson["trigger.dev"] = {
endpointId: options.endpointSlug,
};
// Write the updated package.json file
await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
logger.success(`✅ Wrote trigger.dev config to package.json`);
}
export const resolveOptionsWithPrompts = async (
options: InitCommandOptions,
path: string
): Promise<ResolvedOptions> => {
const resolvedOptions: InitCommandOptions = { ...options };
try {
if (!options.triggerUrl) {
resolvedOptions.triggerUrl = await promptTriggerUrl();
}
if (!options.apiKey) {
resolvedOptions.apiKey = await promptApiKey(resolvedOptions.triggerUrl!);
}
if (!options.endpointSlug) {
resolvedOptions.endpointSlug = await promptEndpointSlug(path);
}
} catch (err) {
// If the user is not calling the command from an interactive terminal, inquirer will throw an error with isTTYError = true
// If this happens, we catch the error, tell the user what has happened, and then continue to run the program with a default trigger project
// Otherwise we have to do some fancy namespace extension logic on the Error type which feels overkill for one line
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (err instanceof Error && (err as any).isTTYError) {
logger.warn(
`'${COMMAND_NAME} init' needs an interactive terminal to provide options`
);
const { shouldContinue } = await inquirer.prompt<{
shouldContinue: boolean;
}>({
name: "shouldContinue",
type: "confirm",
message: `Continue initializing your trigger.dev project?`,
default: true,
});
if (!shouldContinue) {
logger.info("Exiting...");
process.exit(0);
}
} else {
throw err;
}
}
process.exit(1);
});
return resolvedOptions as ResolvedOptions;
};
// Detects if the project is a Next.js project at path
async function detectNextJsProject(path: string): Promise<boolean> {
@@ -227,10 +282,10 @@ async function detectMiddlewareUsage(path: string, usesSrcDir = false) {
if (!matcher || matcher.length === 0) {
logger.warn(
`⚠️ ⚠️ ⚠️ It looks like you're using Next.js middleware at ${pathModule.relative(
`⚠️ ⚠️ ⚠️ It looks like there might be conflicting Next.js middleware in ${pathModule.relative(
process.cwd(),
middlewarePath
)}. This can cause problems with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware for more info.`
)} which can cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
);
return;
@@ -246,10 +301,10 @@ async function detectMiddlewareUsage(path: string, usesSrcDir = false) {
// Check to see if /api/trigger matches the regex, if it does, then we need to output a warning with a link to the docs to fix it
if (matcherRegex.test("/api/trigger")) {
logger.warn(
`🚨 It looks like you're using Next.js middleware at ${pathModule.relative(
`🚨 It looks like there might be conflicting Next.js middleware in ${pathModule.relative(
process.cwd(),
middlewarePath
)} that matches the '/api/trigger' path. This can cause problems with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware for more info.`
)} which will cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
);
}
} else if (
@@ -260,10 +315,10 @@ async function detectMiddlewareUsage(path: string, usesSrcDir = false) {
if (matcherRegexes.some((r) => r.test("/api/trigger"))) {
logger.warn(
`🚨 It looks like you're using Next.js middleware at ${pathModule.relative(
`🚨 It looks like there might be conflicting Next.js middleware in ${pathModule.relative(
process.cwd(),
middlewarePath
)} that matches the '/api/trigger' path. This can cause problems with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware for more info.`
)} which will cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
);
}
}
@@ -305,19 +360,18 @@ async function getMiddlewareConfigMatcher(
async function createTriggerPageRoute(
path: string,
cliResults: CliResults,
options: ResolvedOptions,
usesSrcDir = false
) {
const routeContent = `
import { Job, TriggerClient, eventTrigger } from "@trigger.dev/sdk";
import { createPagesRoute } from "@trigger.dev/nextjs";
const { handler, config } = createPagesRoute(client, { path: "/api/trigger" });
const { handler, config } = createPagesRoute(client);
export { config };
const client = new TriggerClient({
id: "${cliResults.flags.endpointSlug}",
url: process.env.VERCEL_URL,
id: "${options.endpointSlug}",
apiKey: process.env.TRIGGER_API_KEY,
apiUrl: process.env.TRIGGER_API_URL,
});
@@ -360,7 +414,7 @@ export default handler;
async function createTriggerAppRoute(
path: string,
cliResults: CliResults,
options: ResolvedOptions,
usesSrcDir = false
) {
const routeContent = `
@@ -368,8 +422,7 @@ import { Job, TriggerClient, eventTrigger } from "@trigger.dev/sdk";
import { createAppRoute } from "@trigger.dev/nextjs";
const client = new TriggerClient({
id: "${cliResults.flags.endpointSlug}",
url: process.env.VERCEL_URL,
id: "${options.endpointSlug}",
apiKey: process.env.TRIGGER_API_KEY,
apiUrl: process.env.TRIGGER_API_URL,
});
@@ -390,9 +443,7 @@ new Job(client, {
},
});
export const { POST, dynamic } = createAppRoute(client, {
path: "/api/trigger",
});
export const { POST, dynamic } = createAppRoute(client);
`;
const directories = pathModule.join(path, "app", "api", "trigger");
@@ -411,37 +462,30 @@ export const { POST, dynamic } = createAppRoute(client, {
);
}
async function setupEnvironmentVariables(path: string, cliResults: CliResults) {
if (cliResults.flags.apiKey) {
async function setupEnvironmentVariables(
path: string,
options: ResolvedOptions
) {
if (options.apiKey) {
await setupEnvironmentVariable(
path,
".env.local",
"TRIGGER_API_KEY",
cliResults.flags.apiKey,
options.apiKey,
true,
renderApiKey
);
}
if (cliResults.flags.triggerUrl) {
if (options.triggerUrl) {
await setupEnvironmentVariable(
path,
".env.local",
"TRIGGER_API_URL",
cliResults.flags.triggerUrl,
options.triggerUrl,
true
);
}
if (cliResults.flags.endpointUrl) {
await setupEnvironmentVariable(
path,
".env.local",
"VERCEL_URL",
cliResults.flags.endpointUrl,
false
);
}
}
async function setupEnvironmentVariable(
@@ -496,13 +540,3 @@ async function pathExists(path: string): Promise<boolean> {
return false;
}
}
async function waitForProjectToBuild() {
const spinner = ora("Waiting for project to build...").start();
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
spinner.stop();
}
@@ -16,6 +16,6 @@ export const TITLE_TEXT = `
`;
export const DEFAULT_APP_NAME = "my-triggers";
export const COMMAND_NAME = "@trigger.dev/init";
export const COMMAND_NAME = "@trigger.dev/cli";
export const TEMPLATE_ORGANIZATION = "triggerdotdev";
export const DEFAULT_TRIGGER_URL = "https://cloud.trigger.dev";
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env node
import { program } from "./cli/index.js";
import { logger } from "./utils/logger.js";
const main = async () => {
await program.parseAsync();
};
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);
}
process.exit(1);
});
-242
View File
@@ -1,242 +0,0 @@
import { Command } from "commander";
import inquirer from "inquirer";
import { COMMAND_NAME, DEFAULT_TRIGGER_URL } from "../consts.js";
import { getVersion } from "../utils/getVersion.js";
import { logger } from "../utils/logger.js";
export interface CliFlags {
projectPath: string;
triggerUrl: string;
endpointUrl: string;
endpointSlug: string;
apiKey?: string;
}
export interface CliResults {
flags: CliFlags;
}
const defaultOptions: CliResults = {
flags: {
projectPath: ".",
triggerUrl: DEFAULT_TRIGGER_URL,
endpointUrl: "http://localhost:3000",
endpointSlug: "my-nextjs-project",
},
};
export const parseCliOptions = async () => {
const cliResults = defaultOptions;
const program = new Command().name(COMMAND_NAME);
program
.description("A CLI for initializing Trigger.dev in your Next.js")
.option(
"-p, --project-path <project-path>",
"The path to the Next.js project",
"."
)
.option(
"-k, --api-key <api-key>",
"The development API key to use for the project."
)
.option(
"-e, --endpoint-slug <endpoint-slug>",
"The unique slug for the endpoint to use for this project. (e.g. my-nextjs-project)",
"my-nextjs-project"
)
.option(
"-u, --endpoint-url <endpoint-url>",
"The URL of your local Next.js project. (e.g. http://localhost:3000). NOTE: Must be a publicly accessible URL if you are using a deployed Trigger.dev instance"
)
.option(
"-t, --trigger-url <trigger-url>",
"The URL of the Trigger.dev instance to use. (e.g. https://cloud.trigger.dev)"
)
.version(getVersion(), "-v, --version", "Display the version number")
.parse(process.argv);
cliResults.flags = program.opts();
return cliResults;
};
export const runCliPrompts = async (cliResults: CliResults) => {
try {
if (!cliResults.flags.triggerUrl) {
cliResults.flags.triggerUrl = await promptTriggerUrl();
}
if (!cliResults.flags.apiKey) {
cliResults.flags.apiKey = await promptApiKey(cliResults.flags.triggerUrl);
}
if (!cliResults.flags.endpointSlug) {
cliResults.flags.endpointSlug = await promptEndpointSlug();
}
if (!cliResults.flags.endpointUrl) {
cliResults.flags.endpointUrl = await promptEndpointUrl(
cliResults.flags.triggerUrl
);
}
} catch (err) {
// If the user is not calling the command from an interactive terminal, inquirer will throw an error with isTTYError = true
// If this happens, we catch the error, tell the user what has happened, and then continue to run the program with a default trigger project
// Otherwise we have to do some fancy namespace extension logic on the Error type which feels overkill for one line
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (err instanceof Error && (err as any).isTTYError) {
logger.warn(
`${COMMAND_NAME} needs an interactive terminal to provide options`
);
const { shouldContinue } = await inquirer.prompt<{
shouldContinue: boolean;
}>({
name: "shouldContinue",
type: "confirm",
message: `Continue initializing your trigger.dev project?`,
default: true,
});
if (!shouldContinue) {
logger.info("Exiting...");
process.exit(0);
}
} else {
throw err;
}
}
return cliResults;
};
const promptTriggerUrl = async (): Promise<string> => {
const { instanceType } = await inquirer.prompt<{
instanceType: "cloud" | "self-hosted";
}>([
{
type: "list",
name: "instanceType",
message: "Are you using the Trigger.dev cloud or self-hosted?",
choices: [
{
name: "Trigger.dev Cloud",
value: "cloud",
default: true,
},
{
name: "Self hosted",
value: "self-hosted",
},
],
},
]);
if (instanceType === "cloud") {
return DEFAULT_TRIGGER_URL;
}
const { triggerUrl } = await inquirer.prompt<{ triggerUrl: string }>({
type: "input",
name: "triggerUrl",
message: "Enter the URL of your self-hosted Trigger.dev instance",
validate: (input) => {
if (!input) {
return "Please enter the URL of your self-hosted Trigger.dev instance";
}
return true;
},
});
return triggerUrl;
};
const promptApiKey = async (instanceUrl: string): Promise<string> => {
// First prompt if they want to enter their API key now, and if they say Yes, then prompt for it and return it
const { apiKey } = await inquirer.prompt<{ apiKey: string }>({
type: "input",
name: "apiKey",
message: `Enter your development API key (Find yours ➡️ ${instanceUrl})`,
validate: (input) => {
// Make sure they enter something like tr_dev_********
if (!input) {
return "Please enter your development API key";
}
if (!input.startsWith("tr_dev_")) {
return "Please enter a valid development API key or leave blank to skip (should start with tr_dev_)";
}
return true;
},
});
return apiKey;
};
const promptEndpointSlug = async (): Promise<string> => {
const { endpointSlug } = await inquirer.prompt<{
endpointSlug: string;
}>({
type: "input",
name: "endpointSlug",
message: "Enter a unique slug for your endpoint (required)",
validate: (input) => {
if (!input) {
return "Please enter a unique slug for your endpoint";
}
return true;
},
});
return endpointSlug;
};
const promptEndpointUrl = async (instanceUrl: string): Promise<string> => {
const { endpointUrl } = await inquirer.prompt<{
endpointUrl: string;
}>({
type: "input",
name: "endpointUrl",
message: "What's the URL of your Next.js project?",
validate: (input) => {
if (!input) {
return "Please enter the URL of your Next.js project";
}
// If instanceUrl is a cloud instance, then the URL must be publicly accessible
const url = new URL(input);
const triggerUrl = new URL(instanceUrl);
if (triggerUrl.hostname !== "localhost" && url.hostname === "localhost") {
return `Your Trigger.dev instance is hosted at ${triggerUrl.hostname}, so your Next.js project must also be publicly accessible. See our docs for more info: https://trigger.dev/docs/documentation/guides/tunneling-localhost`;
}
// Make sure triggerUrl and url don't use the same port if they are both localhost
if (
triggerUrl.hostname === "localhost" &&
url.hostname === "localhost" &&
triggerUrl.port === url.port
) {
return `Your Trigger.dev instance and your Next.js project are both trying to use port ${triggerUrl.port}. Please use a different port for one of them`;
}
return true;
},
});
return endpointUrl;
};
export const obfuscateApiKey = (apiKey: string) => {
const [prefix, slug, secretPart] = apiKey.split("_") as [
string,
string,
string
];
return `${prefix}_${slug}_${"*".repeat(secretPart.length)}`;
};
+5 -20
View File
@@ -2,21 +2,12 @@ import type { TriggerClient } from "@trigger.dev/sdk";
import type { NextApiRequest, NextApiResponse } from "next";
import { NextResponse } from "next/server";
export type TriggerHandlerOptions = {
path: string;
};
export function createPagesRoute(
client: TriggerClient,
options: TriggerHandlerOptions
) {
client.path = options.path;
export function createPagesRoute(client: TriggerClient) {
const handler = async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const normalizedRequest = await convertToStandardRequest(client.url, req);
const normalizedRequest = await convertToStandardRequest(req);
const response = await client.handleRequest(normalizedRequest);
@@ -39,12 +30,7 @@ export function createPagesRoute(
};
}
export function createAppRoute(
client: TriggerClient,
options: TriggerHandlerOptions
) {
client.path = options.path;
export function createAppRoute(client: TriggerClient) {
const POST = async function handler(req: Request) {
const response = await client.handleRequest(req);
@@ -64,7 +50,6 @@ export function createAppRoute(
}
async function convertToStandardRequest(
url: string,
nextReq: NextApiRequest
): Promise<Request> {
const { headers: nextHeaders, method } = nextReq;
@@ -75,8 +60,8 @@ async function convertToStandardRequest(
headers.set(key, value as string);
});
// Create a new Request object
const webReq = new Request(url, {
// Create a new Request object (hardcode the url because it doesn't really matter what it is)
const webReq = new Request("https://next.js/api/trigger", {
headers,
method,
// @ts-ignore
+1 -44
View File
@@ -1,8 +1,8 @@
import {
ErrorWithStackSchema,
IndexEndpointResponse,
HandleTriggerSource,
HttpSourceRequestHeadersSchema,
IndexEndpointResponse,
InitializeTriggerBodySchema,
LogLevel,
Logger,
@@ -46,16 +46,11 @@ const registerSourceEvent: EventSpecification<RegisterSourceEvent> = {
export type TriggerClientOptions = {
id: string;
url?: string;
apiKey?: string;
apiUrl?: string;
logLevel?: LogLevel;
};
export type ListenOptions = {
url: string;
};
export class TriggerClient {
#options: TriggerClientOptions;
#registeredJobs: Record<string, Job<Trigger<EventSpecification<any>>, any>> =
@@ -84,24 +79,15 @@ export class TriggerClient {
#client: ApiClient;
#logger: Logger;
private _url: string;
id: string;
path?: string;
constructor(options: TriggerClientOptions) {
this.id = options.id;
this._url = buildClientUrl(options.url);
this.#options = options;
this.#client = new ApiClient(this.#options);
this.#logger = new Logger("trigger.dev", this.#options.logLevel);
}
get url() {
return `${this._url}${
this.path ? `${this.path.startsWith("/") ? "" : "/"}${this.path}` : ""
}`;
}
async handleRequest(request: Request): Promise<NormalizedResponse> {
this.#logger.debug("handling request", {
url: request.url,
@@ -812,32 +798,3 @@ export class TriggerClient {
};
}
}
function buildClientUrl(url?: string): string {
if (!url) {
// Try and get the host from the environment
const host =
process.env.TRIGGER_CLIENT_HOST ??
process.env.HOST ??
process.env.HOSTNAME ??
process.env.NOW_URL ??
process.env.VERCEL_URL;
// If the host is set, we return it + the path
if (host) {
return "https://" + host;
}
// If we can't get the host, we throw an error
throw new Error(
"Could not determine the url for this TriggerClient. Please set the TRIGGER_CLIENT_HOST environment variable or pass in the `url` option to the TriggerClient constructor."
);
}
// Check to see if url has the protocol, and if it doesn't, add it
if (!url.startsWith("http")) {
return "https://" + url;
}
return url;
}
+168 -96
View File
@@ -461,9 +461,9 @@ importers:
examples/nextjs-clerk:
specifiers:
'@clerk/nextjs': ^4.21.12
'@trigger.dev/init': workspace:*
'@trigger.dev/nextjs': 1.0.0-next.5
'@trigger.dev/sdk': 2.0.0-next.4
'@trigger.dev/cli': workspace:*
'@trigger.dev/nextjs': ^1.0.0-next.5
'@trigger.dev/sdk': ^2.0.0-next.4
'@types/node': 20.3.2
'@types/react': ^18.0.21
'@types/react-dom': ^18.0.6
@@ -489,7 +489,7 @@ importers:
tailwindcss: 3.3.2
typescript: 5.1.6
devDependencies:
'@trigger.dev/init': link:../../packages/init-trigger
'@trigger.dev/cli': link:../../packages/cli
examples/nextjs-example:
specifiers:
@@ -626,6 +626,61 @@ importers:
rimraf: 3.0.2
tsup: 6.6.3
packages/cli:
specifiers:
'@types/degit': ^2.8.3
'@types/fs-extra': ^11.0.1
'@types/gradient-string': ^1.1.2
'@types/inquirer': ^9.0.3
'@types/node': '16'
'@types/node-fetch': ^2.6.2
chalk: ^5.2.0
commander: ^9.4.1
degit: ^2.8.4
execa: ^7.0.0
fs-extra: ^11.1.0
gradient-string: ^2.0.2
inquirer: ^9.1.4
localtunnel: ^2.0.2
ngrok: 5.0.0-beta.2
node-fetch: ^3.3.0
ora: ^6.1.2
path-to-regexp: ^6.2.1
rimraf: ^3.0.2
simple-git: ^3.19.0
terminal-link: ^3.0.0
tsup: ^6.5.0
type-fest: ^3.6.0
typescript: ^4.9.5
zod: ^3.20.2
dependencies:
'@types/degit': 2.8.3
chalk: 5.2.0
commander: 9.5.0
degit: 2.8.4
execa: 7.0.0
fs-extra: 11.1.0
gradient-string: 2.0.2
inquirer: 9.1.4
localtunnel: 2.0.2
ngrok: 5.0.0-beta.2
node-fetch: 3.3.0
ora: 6.1.2
path-to-regexp: 6.2.1
simple-git: 3.19.0
terminal-link: 3.0.0
zod: 3.21.4
devDependencies:
'@types/fs-extra': 11.0.1
'@types/gradient-string': 1.1.2
'@types/inquirer': 9.0.3
'@types/node': 16.18.11
'@types/node-fetch': 2.6.2
rimraf: 3.0.2
tsup: 6.6.3_typescript@4.9.5
type-fest: 3.6.0
typescript: 4.9.5
packages/database:
specifiers:
'@prisma/client': ^4.16.0
@@ -684,57 +739,6 @@ importers:
'@types/react': 18.0.26
typescript: 4.9.5
packages/init-trigger:
specifiers:
'@types/degit': ^2.8.3
'@types/fs-extra': ^11.0.1
'@types/gradient-string': ^1.1.2
'@types/inquirer': ^9.0.3
'@types/node': '16'
'@types/node-fetch': ^2.6.2
chalk: ^5.2.0
commander: ^9.4.1
degit: ^2.8.4
execa: ^7.0.0
fs-extra: ^11.1.0
gradient-string: ^2.0.2
inquirer: ^9.1.4
node-fetch: ^3.3.0
ora: ^6.1.2
path-to-regexp: ^6.2.1
rimraf: ^3.0.2
simple-git: ^3.19.0
terminal-link: ^3.0.0
tsup: ^6.5.0
type-fest: ^3.6.0
typescript: ^4.9.5
zod: ^3.20.2
dependencies:
'@types/degit': 2.8.3
chalk: 5.2.0
commander: 9.5.0
degit: 2.8.4
execa: 7.0.0
fs-extra: 11.1.0
gradient-string: 2.0.2
inquirer: 9.1.4
node-fetch: 3.3.0
ora: 6.1.2
path-to-regexp: 6.2.1
simple-git: 3.19.0
terminal-link: 3.0.0
zod: 3.21.4
devDependencies:
'@types/fs-extra': 11.0.1
'@types/gradient-string': 1.1.2
'@types/inquirer': 9.0.3
'@types/node': 16.18.11
'@types/node-fetch': 2.6.2
rimraf: 3.0.2
tsup: 6.6.3_typescript@4.9.5
type-fest: 3.6.0
typescript: 4.9.5
packages/integration-kit:
specifiers:
'@trigger.dev/tsconfig': workspace:*
@@ -9024,7 +9028,6 @@ packages:
/@sindresorhus/is/4.6.0:
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'}
dev: true
/@slack/logger/3.0.0:
resolution: {integrity: sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==}
@@ -10524,7 +10527,6 @@ packages:
engines: {node: '>=10'}
dependencies:
defer-to-connect: 2.0.1
dev: true
/@tailwindcss/forms/0.5.3_tailwindcss@3.1.8:
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
@@ -10824,9 +10826,8 @@ packages:
dependencies:
'@types/http-cache-semantics': 4.0.1
'@types/keyv': 3.1.4
'@types/node': 20.3.1
'@types/node': 20.3.2
'@types/responselike': 1.0.0
dev: true
/@types/chai-subset/1.3.3:
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
@@ -10964,7 +10965,7 @@ packages:
resolution: {integrity: sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==}
dependencies:
'@types/jsonfile': 6.1.1
'@types/node': 20.3.1
'@types/node': 20.3.2
dev: true
/@types/glob/7.2.0:
@@ -11004,7 +11005,6 @@ packages:
/@types/http-cache-semantics/4.0.1:
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
dev: true
/@types/humanize-duration/3.27.1:
resolution: {integrity: sha512-K3e+NZlpCKd6Bd/EIdqjFJRFHbrq5TzPPLwREk5Iv/YoIjQrs6ljdAUCo+Lb2xFlGNOjGSE0dqsVD19cZL137w==}
@@ -11075,7 +11075,7 @@ packages:
/@types/jsonfile/6.1.1:
resolution: {integrity: sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==}
dependencies:
'@types/node': 20.3.1
'@types/node': 20.3.2
dev: true
/@types/jsonwebtoken/9.0.1:
@@ -11090,8 +11090,7 @@ packages:
/@types/keyv/3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
dependencies:
'@types/node': 20.3.1
dev: true
'@types/node': 20.3.2
/@types/lodash/4.14.191:
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
@@ -11244,8 +11243,7 @@ packages:
/@types/responselike/1.0.0:
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
dependencies:
'@types/node': 20.3.1
dev: true
'@types/node': 20.3.2
/@types/retry/0.12.0:
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
@@ -11309,7 +11307,7 @@ packages:
/@types/through/0.0.30:
resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==}
dependencies:
'@types/node': 20.3.1
'@types/node': 20.3.2
dev: true
/@types/tinycolor2/1.4.3:
@@ -11357,8 +11355,7 @@ packages:
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
requiresBuild: true
dependencies:
'@types/node': 20.3.1
dev: true
'@types/node': 20.3.2
optional: true
/@typescript-eslint/eslint-plugin/5.59.6_bhjpu5ld5qmewts22wsycix4mi:
@@ -12424,10 +12421,18 @@ packages:
engines: {node: '>=4'}
dev: true
/axios/0.21.4_debug@4.3.2:
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies:
follow-redirects: 1.15.2_debug@4.3.2
transitivePeerDependencies:
- debug
dev: false
/axios/0.25.0_debug@4.3.4:
resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
dependencies:
follow-redirects: 1.15.2
follow-redirects: 1.15.2_debug@4.3.2
transitivePeerDependencies:
- debug
dev: true
@@ -12435,7 +12440,7 @@ packages:
/axios/0.26.1:
resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==}
dependencies:
follow-redirects: 1.15.2
follow-redirects: 1.15.2_debug@4.3.2
transitivePeerDependencies:
- debug
dev: false
@@ -12443,7 +12448,7 @@ packages:
/axios/0.27.2:
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
dependencies:
follow-redirects: 1.15.2
follow-redirects: 1.15.2_debug@4.3.2
form-data: 4.0.0
transitivePeerDependencies:
- debug
@@ -12452,7 +12457,7 @@ packages:
/axios/1.4.0:
resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==}
dependencies:
follow-redirects: 1.15.2
follow-redirects: 1.15.2_debug@4.3.2
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
@@ -12881,7 +12886,6 @@ packages:
/buffer-crc32/0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
dev: true
/buffer-equal-constant-time/1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
@@ -13024,7 +13028,6 @@ packages:
/cacheable-lookup/5.0.4:
resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
engines: {node: '>=10.6.0'}
dev: true
/cacheable-request/6.1.0:
resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==}
@@ -13050,7 +13053,6 @@ packages:
lowercase-keys: 2.0.0
normalize-url: 6.1.0
responselike: 2.0.1
dev: true
/cachedir/2.3.0:
resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==}
@@ -13429,7 +13431,6 @@ packages:
resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
dependencies:
mimic-response: 1.0.1
dev: true
/clone/1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
@@ -14012,6 +14013,17 @@ packages:
supports-color: 8.1.1
dev: true
/debug/4.3.2:
resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
/debug/4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@@ -14072,7 +14084,6 @@ packages:
engines: {node: '>=10'}
dependencies:
mimic-response: 3.1.0
dev: true
/dedent/0.7.0:
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
@@ -14144,7 +14155,6 @@ packages:
/defer-to-connect/2.0.1:
resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
engines: {node: '>=10'}
dev: true
/define-lazy-prop/2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
@@ -14495,7 +14505,6 @@ packages:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
dependencies:
once: 1.4.0
dev: true
/endent/2.1.0:
resolution: {integrity: sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==}
@@ -15995,6 +16004,20 @@ packages:
- supports-color
dev: true
/extract-zip/2.0.1:
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
engines: {node: '>= 10.17.0'}
hasBin: true
dependencies:
debug: 4.3.4
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
'@types/yauzl': 2.10.0
transitivePeerDependencies:
- supports-color
dev: false
/extract-zip/2.0.1_supports-color@8.1.1:
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
engines: {node: '>= 10.17.0'}
@@ -16117,7 +16140,6 @@ packages:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
dependencies:
pend: 1.2.0
dev: true
/fetch-blob/3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
@@ -16273,7 +16295,7 @@ packages:
engines: {node: '>=0.4.0'}
dev: true
/follow-redirects/1.15.2:
/follow-redirects/1.15.2_debug@4.3.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
peerDependencies:
@@ -16281,6 +16303,8 @@ packages:
peerDependenciesMeta:
debug:
optional: true
dependencies:
debug: 4.3.2
/for-each/0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -16616,7 +16640,6 @@ packages:
engines: {node: '>=8'}
dependencies:
pump: 3.0.0
dev: true
/get-stream/6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
@@ -16908,7 +16931,6 @@ packages:
lowercase-keys: 2.0.0
p-cancelable: 2.1.1
responselike: 2.0.1
dev: true
/got/9.6.0:
resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==}
@@ -17133,6 +17155,12 @@ packages:
resolution: {integrity: sha512-2zuLt85Ta+gIyvs4N88pCYskNrxf1TFv3LR9t5mdAZIX8BcgQQ48F2opUptvHa6m8zsy5v/a0i9mWzTrlNWU0Q==}
dev: false
/hpagent/0.1.2:
resolution: {integrity: sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==}
requiresBuild: true
dev: false
optional: true
/html-entities/2.3.3:
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
dev: true
@@ -17258,7 +17286,6 @@ packages:
dependencies:
quick-lru: 5.1.1
resolve-alpn: 1.2.1
dev: true
/https-proxy-agent/4.0.0:
resolution: {integrity: sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==}
@@ -18636,6 +18663,19 @@ packages:
engines: {node: '>=14'}
dev: true
/localtunnel/2.0.2:
resolution: {integrity: sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug==}
engines: {node: '>=8.3.0'}
hasBin: true
dependencies:
axios: 0.21.4_debug@4.3.2
debug: 4.3.2
openurl: 1.1.1
yargs: 17.1.1
transitivePeerDependencies:
- supports-color
dev: false
/locate-path/3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
engines: {node: '>=6'}
@@ -18664,6 +18704,10 @@ packages:
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
dev: true
/lodash.clonedeep/4.5.0:
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
dev: false
/lodash.debounce/4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
dev: true
@@ -18757,7 +18801,6 @@ packages:
/lowercase-keys/2.0.0:
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
engines: {node: '>=8'}
dev: true
/lru-cache/4.1.5:
resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
@@ -19430,12 +19473,10 @@ packages:
/mimic-response/1.0.1:
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
engines: {node: '>=4'}
dev: true
/mimic-response/3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
dev: true
/min-indent/1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
@@ -19886,6 +19927,23 @@ packages:
- babel-plugin-macros
dev: false
/ngrok/5.0.0-beta.2:
resolution: {integrity: sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ==}
engines: {node: '>=14.2'}
hasBin: true
requiresBuild: true
dependencies:
extract-zip: 2.0.1
got: 11.8.6
lodash.clonedeep: 4.5.0
uuid: 8.3.2
yaml: 2.3.1
optionalDependencies:
hpagent: 0.1.2
transitivePeerDependencies:
- supports-color
dev: false
/nice-try/1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
dev: true
@@ -20019,7 +20077,6 @@ packages:
/normalize-url/6.1.0:
resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
engines: {node: '>=10'}
dev: true
/npm-run-all/4.1.5:
resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==}
@@ -20286,6 +20343,10 @@ packages:
resolution: {integrity: sha512-XpeCy01X6L5EpP+6Hc3jWN7rMZJ+/k1lwki/kTmWzbVhdPie3jd5O2ZtedEx8Yp58icJ0osVldLMrTB/zslQXA==}
dev: false
/openurl/1.1.1:
resolution: {integrity: sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==}
dev: false
/optionator/0.8.3:
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
engines: {node: '>= 0.8.0'}
@@ -20374,7 +20435,6 @@ packages:
/p-cancelable/2.1.1:
resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
engines: {node: '>=8'}
dev: true
/p-event/4.2.0:
resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==}
@@ -20693,7 +20753,6 @@ packages:
/pend/1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
dev: true
/performance-now/2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
@@ -21430,7 +21489,6 @@ packages:
dependencies:
end-of-stream: 1.4.4
once: 1.4.0
dev: true
/pumpify/1.5.1:
resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==}
@@ -21500,7 +21558,6 @@ packages:
/quick-lru/5.1.1:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
dev: true
/ramda/0.28.0:
resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==}
@@ -22304,7 +22361,6 @@ packages:
/resolve-alpn/1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
dev: true
/resolve-from/4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
@@ -22370,7 +22426,6 @@ packages:
resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==}
dependencies:
lowercase-keys: 2.0.0
dev: true
/restore-cursor/3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
@@ -25638,6 +25693,11 @@ packages:
resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
engines: {node: '>= 14'}
/yaml/2.3.1:
resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==}
engines: {node: '>= 14'}
dev: false
/yargs-parser/18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
@@ -25683,6 +25743,19 @@ packages:
y18n: 5.0.8
yargs-parser: 20.2.9
/yargs/17.1.1:
resolution: {integrity: sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==}
engines: {node: '>=12'}
dependencies:
cliui: 7.0.4
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 20.2.9
dev: false
/yargs/17.6.2:
resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==}
engines: {node: '>=12'}
@@ -25700,7 +25773,6 @@ packages:
dependencies:
buffer-crc32: 0.2.13
fd-slicer: 1.1.0
dev: true
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}