From 3f1d07c8c85896bf971a8a7afcfd51ec62ad2e79 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 12 Sep 2023 13:31:22 +0100 Subject: [PATCH 01/53] Early work defining CLI framework support --- packages/cli/src/frameworks/index.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/cli/src/frameworks/index.ts diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts new file mode 100644 index 00000000000..dc19ade9815 --- /dev/null +++ b/packages/cli/src/frameworks/index.ts @@ -0,0 +1,17 @@ +import { PackageManager } from "../utils/getUserPkgManager"; + +type Dependency = { + name: string; + /** Defaults to "latest" */ + tag?: string; + /** Defaults to prod */ + type?: "dev" | "prod"; +}; + +export interface Framework { + id: string; + name: string; + isMatch(path: string, packageManager: PackageManager): Promise; + dependencies(path: string, packageManager: PackageManager): Promise; + possibleEnvFilenames(): string[]; +} From bbd9e03124bf82dd15d953394584b676d7b1e16c Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 12 Sep 2023 19:21:13 +0100 Subject: [PATCH 02/53] WIP moving CLI init logic to the Framework class --- packages/cli/src/commands/init.ts | 19 ++++---- packages/cli/src/frameworks/index.ts | 31 ++++++++---- packages/cli/src/frameworks/nextjs.ts | 47 +++++++++++++++++++ packages/cli/src/utils/addDependencies.ts | 2 + packages/cli/src/utils/detectNextJsProject.ts | 29 ------------ 5 files changed, 81 insertions(+), 47 deletions(-) create mode 100644 packages/cli/src/frameworks/nextjs.ts delete mode 100644 packages/cli/src/utils/detectNextJsProject.ts diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 5ca61ad30f2..e6ee48dbc6f 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -11,13 +11,14 @@ import { promptApiKey, promptTriggerUrl } from "../cli/index"; import { CLOUD_API_URL, CLOUD_TRIGGER_URL, COMMAND_NAME } from "../consts"; import { TelemetryClient, telemetryClient } from "../telemetry/telemetry"; import { addDependencies } from "../utils/addDependencies"; -import { detectNextJsProject } from "../utils/detectNextJsProject"; import { pathExists, readJSONFile } from "../utils/fileSystem"; import { logger } from "../utils/logger"; import { resolvePath } from "../utils/parseNameAndPath"; import { renderApiKey } from "../utils/renderApiKey"; import { renderTitle } from "../utils/renderTitle"; import { TriggerApi, WhoamiResponse } from "../utils/triggerApi"; +import { frameworkNames, getFramework } from "../frameworks"; +import { getUserPackageManager } from "../utils/getUserPkgManager"; export type InitCommandOptions = { projectPath: string; @@ -44,21 +45,19 @@ export const initCommand = async (options: InitCommandOptions) => { logger.info(`✨ Initializing Trigger.dev in project`); } - // Detect if are are in a Next.js project - const isNextJsProject = await detectNextJsProject(resolvedPath); + const packageManager = await getUserPackageManager(resolvedPath); + const framework = await getFramework(resolvedPath, packageManager); - if (!isNextJsProject) { + if (!framework) { logger.error( - "We currently only support automatic setup for Next.js projects (we didn't detect one). View our manual installation guides for all frameworks: https://trigger.dev/docs/documentation/quickstarts/introduction" + `We currently only support automatic setup for ${frameworkNames()} projects (we didn't detect one). View our manual installation guides for all frameworks: https://trigger.dev/docs/documentation/quickstarts/introduction` ); - telemetryClient.init.failed("not_nextjs_project", options); + telemetryClient.init.failed("not_supported_project", options); return; - } else { - logger.success("βœ… Detected Next.js project"); } + logger.success(`βœ… Detected ${framework.name} project`); const hasGitChanges = await detectGitChanges(resolvedPath); - if (hasGitChanges) { // Warn the user that they have git changes logger.warn( @@ -103,6 +102,8 @@ export const initCommand = async (options: InitCommandOptions) => { const endpointSlug = authorizedKey.project.slug; const resolvedOptions: ResolvedOptions = { ...optionsAfterPrompts, endpointSlug }; + const dependencies = await framework.dependencies(resolvedPath, packageManager); + await addDependencies(resolvedPath, [ { name: "@trigger.dev/sdk", tag: "latest" }, { name: "@trigger.dev/nextjs", tag: "latest" }, diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts index dc19ade9815..73c0434fdb9 100644 --- a/packages/cli/src/frameworks/index.ts +++ b/packages/cli/src/frameworks/index.ts @@ -1,17 +1,30 @@ +import { InstallPackage } from "../utils/addDependencies"; import { PackageManager } from "../utils/getUserPkgManager"; - -type Dependency = { - name: string; - /** Defaults to "latest" */ - tag?: string; - /** Defaults to prod */ - type?: "dev" | "prod"; -}; +import { NextJs } from "./nextjs"; export interface Framework { id: string; name: string; isMatch(path: string, packageManager: PackageManager): Promise; - dependencies(path: string, packageManager: PackageManager): Promise; + dependencies(): Promise; possibleEnvFilenames(): string[]; } + +const frameworks: Framework[] = [new NextJs()]; + +export const getFramework = async ( + path: string, + packageManager: PackageManager +): Promise => { + for (const framework of frameworks) { + if (await framework.isMatch(path, packageManager)) { + return framework; + } + } + + return; +}; + +export function frameworkNames() { + return frameworks.map((f) => f.name).join(", "); +} diff --git a/packages/cli/src/frameworks/nextjs.ts b/packages/cli/src/frameworks/nextjs.ts new file mode 100644 index 00000000000..fe997750c50 --- /dev/null +++ b/packages/cli/src/frameworks/nextjs.ts @@ -0,0 +1,47 @@ +import { Framework } from "."; +import { InstallPackage } from "../utils/addDependencies"; +import { PackageManager } from "../utils/getUserPkgManager"; +import fs from "fs/promises"; +import pathModule from "path"; +import { readPackageJson } from "../utils/readPackageJson"; + +export class NextJs implements Framework { + id = "nextjs"; + name = "Next.js"; + + async isMatch(path: string, packageManager: PackageManager): Promise { + const hasNextConfigFile = await detectNextConfigFile(path); + if (hasNextConfigFile) { + return true; + } + + return await detectNextDependency(path); + } + + async dependencies(): Promise { + return [ + { name: "@trigger.dev/sdk", tag: "latest" }, + { name: "@trigger.dev/nextjs", tag: "latest" }, + ]; + } + + possibleEnvFilenames(): string[] { + throw new Error("Method not implemented."); + } +} + +async function detectNextConfigFile(path: string): Promise { + return fs + .access(pathModule.join(path, "next.config.js")) + .then(() => true) + .catch(() => false); +} + +async function detectNextDependency(path: string): Promise { + const packageJsonContent = await readPackageJson(path); + if (!packageJsonContent) { + return false; + } + + return packageJsonContent.dependencies?.next !== undefined; +} diff --git a/packages/cli/src/utils/addDependencies.ts b/packages/cli/src/utils/addDependencies.ts index 2e9f3ce0def..500c183c717 100644 --- a/packages/cli/src/utils/addDependencies.ts +++ b/packages/cli/src/utils/addDependencies.ts @@ -10,6 +10,8 @@ import { z } from "zod"; export type InstallPackage = { name: string; tag: string; + /** Defaults to prod */ + type?: "dev" | "prod"; }; export type InstalledPackage = { diff --git a/packages/cli/src/utils/detectNextJsProject.ts b/packages/cli/src/utils/detectNextJsProject.ts deleted file mode 100644 index 664501f8749..00000000000 --- a/packages/cli/src/utils/detectNextJsProject.ts +++ /dev/null @@ -1,29 +0,0 @@ -import fs from "fs/promises"; -import pathModule from "path"; -import { readPackageJson } from "./readPackageJson"; - -/** Detects if the project is a Next.js project at path */ -export async function detectNextJsProject(path: string): Promise { - const hasNextConfigFile = await detectNextConfigFile(path); - if (hasNextConfigFile) { - return true; - } - - return await detectNextDependency(path); -} - -async function detectNextConfigFile(path: string): Promise { - return fs - .access(pathModule.join(path, "next.config.js")) - .then(() => true) - .catch(() => false); -} - -async function detectNextDependency(path: string): Promise { - const packageJsonContent = await readPackageJson(path); - if (!packageJsonContent) { - return false; - } - - return packageJsonContent.dependencies?.next !== undefined; -} From 031e9b25f90882bd95cfec3a1386adc745bca7eb Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 14 Sep 2023 17:53:34 +0100 Subject: [PATCH 03/53] Installing files should now work for Nextjs --- packages/cli/src/commands/init.ts | 556 ++--------------- packages/cli/src/frameworks/index.ts | 9 + packages/cli/src/frameworks/nextjs.ts | 47 -- packages/cli/src/frameworks/nextjs/index.ts | 415 +++++++++++++ .../cli/src/frameworks/nextjs/middleware.ts | 93 +++ packages/cli/src/telemetry/telemetry.ts | 22 +- packages/cli/src/utils/addDependencies.ts | 2 - packages/cli/src/utils/env.ts | 75 +++ packages/cli/src/utils/removeFileExtension.ts | 3 + pnpm-lock.yaml | 580 +++++++++++++++++- 10 files changed, 1199 insertions(+), 603 deletions(-) delete mode 100644 packages/cli/src/frameworks/nextjs.ts create mode 100644 packages/cli/src/frameworks/nextjs/index.ts create mode 100644 packages/cli/src/frameworks/nextjs/middleware.ts create mode 100644 packages/cli/src/utils/env.ts create mode 100644 packages/cli/src/utils/removeFileExtension.ts diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index e6ee48dbc6f..185139c487c 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -6,19 +6,24 @@ 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, promptTriggerUrl } from "../cli/index"; import { CLOUD_API_URL, CLOUD_TRIGGER_URL, COMMAND_NAME } from "../consts"; -import { TelemetryClient, telemetryClient } from "../telemetry/telemetry"; +import { telemetryClient } from "../telemetry/telemetry"; import { addDependencies } from "../utils/addDependencies"; import { pathExists, readJSONFile } from "../utils/fileSystem"; import { logger } from "../utils/logger"; import { resolvePath } from "../utils/parseNameAndPath"; -import { renderApiKey } from "../utils/renderApiKey"; import { renderTitle } from "../utils/renderTitle"; import { TriggerApi, WhoamiResponse } from "../utils/triggerApi"; import { frameworkNames, getFramework } from "../frameworks"; import { getUserPackageManager } from "../utils/getUserPkgManager"; +import { + getEnvFilename, + setApiKeyEnvironmentVariable, + setApiUrlEnvironmentVariable, +} from "../utils/env"; +import { readPackageJson } from "../utils/readPackageJson"; export type InitCommandOptions = { projectPath: string; @@ -68,11 +73,7 @@ export const initCommand = async (options: InitCommandOptions) => { const isTypescriptProject = await detectTypescriptProject(resolvedPath); telemetryClient.init.isTypescriptProject(isTypescriptProject, options); - const optionsAfterPrompts = await resolveOptionsWithPrompts( - options, - resolvedPath, - telemetryClient - ); + const optionsAfterPrompts = await resolveOptionsWithPrompts(options, resolvedPath); const apiKey = optionsAfterPrompts.apiKey; if (!apiKey) { @@ -102,49 +103,31 @@ export const initCommand = async (options: InitCommandOptions) => { const endpointSlug = authorizedKey.project.slug; const resolvedOptions: ResolvedOptions = { ...optionsAfterPrompts, endpointSlug }; - const dependencies = await framework.dependencies(resolvedPath, packageManager); - - await addDependencies(resolvedPath, [ - { name: "@trigger.dev/sdk", tag: "latest" }, - { name: "@trigger.dev/nextjs", tag: "latest" }, - ]); - + //install dependencies + const dependencies = await framework.dependencies(); + await addDependencies(resolvedPath, dependencies); telemetryClient.init.addedDependencies(resolvedOptions); - // Setup environment variables - await setupEnvironmentVariables(resolvedPath, resolvedOptions); - - const usesSrcDir = await detectUseOfSrcDir(resolvedPath); - - if (usesSrcDir) { - logger.info("πŸ“ Detected use of src directory"); + // Setup environment variables (create a file if there isn't one) + let envName = await getEnvFilename(resolvedPath, framework.possibleEnvFilenames()); + if (!envName) { + envName = pathModule.join(resolvedPath, framework.possibleEnvFilenames()[0]!); + await fs.writeFile(envName, ""); } + await setApiKeyEnvironmentVariable(resolvedPath, envName, resolvedOptions.apiKey); + await setApiUrlEnvironmentVariable(resolvedPath, envName, resolvedOptions.apiUrl); - const nextJsDir = await detectPagesOrAppDir(resolvedPath, usesSrcDir); + const installOptions = { + typescript: isTypescriptProject, + packageManager, + endpointSlug: resolvedOptions.endpointSlug, + }; - const routeDir = pathModule.join(resolvedPath, usesSrcDir ? "src" : ""); + telemetryClient.init.install(resolvedOptions, framework.name, installOptions); + await framework.install(resolvedPath, installOptions); - if (nextJsDir === "pages") { - telemetryClient.init.createFiles(resolvedOptions, "pages"); - await createTriggerPageRoute( - resolvedPath, - routeDir, - resolvedOptions, - isTypescriptProject, - usesSrcDir - ); - } else { - telemetryClient.init.createFiles(resolvedOptions, "app"); - await createTriggerAppRoute( - resolvedPath, - routeDir, - resolvedOptions, - isTypescriptProject, - usesSrcDir - ); - } - - await detectMiddlewareUsage(resolvedPath, usesSrcDir); + telemetryClient.init.postInstall(resolvedOptions, framework.name, installOptions); + await framework.postInstall(resolvedPath, installOptions); await addConfigurationToPackageJson(resolvedPath, resolvedOptions); @@ -170,15 +153,18 @@ async function printNextSteps(options: ResolvedOptions, authorizedKey: WhoamiRes } 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()); + const pkgJson = await readPackageJson(path); + + if (!pkgJson) { + throw new Error("Could not find package.json"); + } pkgJson["trigger.dev"] = { endpointId: options.endpointSlug, }; // Write the updated package.json file + const pkgJsonPath = pathModule.join(path, "package.json"); await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2)); logger.success(`βœ… Wrote trigger.dev config to package.json`); @@ -190,8 +176,7 @@ type OptionsAfterPrompts = Required> & const resolveOptionsWithPrompts = async ( options: InitCommandOptions, - path: string, - telemetryClient: TelemetryClient + path: string ): Promise => { const resolvedOptions: InitCommandOptions = { ...options }; @@ -280,474 +265,3 @@ async function detectTypescriptProject(path: string): Promise { return false; } } - -async function detectUseOfSrcDir(path: string): Promise { - // Detects if the project is using a src directory - try { - await fs.access(pathModule.join(path, "src")); - return true; - } catch (error) { - return false; - } -} - -// Detect the use of pages or app dir in the Next.js project -// Import the next.config.js file and check for experimental: { appDir: true } -async function detectPagesOrAppDir(path: string, usesSrcDir = false): Promise<"pages" | "app"> { - const nextConfigPath = pathModule.join(path, "next.config.js"); - const importedConfig = await import(pathToFileURL(nextConfigPath).toString()).catch(() => ({})); - - if (importedConfig?.default?.experimental?.appDir) { - return "app"; - } else { - // We need to check if src/app/page.tsx exists - // Or app/page.tsx exists - // If so then we return app - // If not return pages - - const extensionsToCheck = ["jsx", "tsx", "js", "ts"]; - const basePath = pathModule.join(path, usesSrcDir ? "src" : "", "app", `page.`); - - for (const extension of extensionsToCheck) { - const appPagePath = basePath + extension; - const appPageExists = await pathExists(appPagePath); - - if (appPageExists) { - return "app"; - } - } - - return "pages"; - } -} - -async function detectMiddlewareUsage(path: string, usesSrcDir = false) { - const middlewarePath = pathModule.join(path, usesSrcDir ? "src" : "", "middleware.ts"); - - const middlewareExists = await pathExists(middlewarePath); - - if (!middlewareExists) { - return; - } - - const matcher = await getMiddlewareConfigMatcher(middlewarePath); - - if (!matcher || matcher.length === 0) { - logger.warn( - `⚠️ ⚠️ ⚠️ It looks like there might be conflicting Next.js middleware in ${pathModule.relative( - process.cwd(), - middlewarePath - )} 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; - } - - if (matcher.length === 0) { - return; - } - - if (typeof matcher === "string") { - const matcherRegex = pathToRegexp(matcher); - - // 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 there might be conflicting Next.js middleware in ${pathModule.relative( - process.cwd(), - 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)); - - if (matcherRegexes.some((r) => r.test("/api/trigger"))) { - logger.warn( - `🚨 It looks like there might be conflicting Next.js middleware in ${pathModule.relative( - process.cwd(), - 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 }); - } - } -} - -async function getMiddlewareConfigMatcher(path: string): Promise> { - const fileContent = await fs.readFile(path, "utf-8"); - - const regex = /matcher:\s*(\[.*\]|".*")/s; - let match = regex.exec(fileContent); - - if (!match) { - return []; - } - - if (match.length < 2) { - return []; - } - - let matcherString: string = match[1] as string; - - // Handle array scenario - if (matcherString.startsWith("[") && matcherString.endsWith("]")) { - matcherString = matcherString.slice(1, -1); // Remove brackets - const arrayRegex = /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/g; - let arrayMatch; - const matches: string[] = []; - while ((arrayMatch = arrayRegex.exec(matcherString)) !== null) { - matches.push((arrayMatch[1] as string).slice(1, -1)); // remove quotes - } - return matches; - } else { - // Handle single string scenario - return [matcherString.slice(1, -1)]; // remove quotes - } -} - -// Find the alias that points to the "src" directory. -// So for example, the paths object could be: -// { -// "@/*": ["./src/*"] -// } -// In this case, we would return "@" -function getPathAlias(tsconfig: any, usesSrcDir: boolean) { - if (!tsconfig.compilerOptions.paths) { - return; - } - - const paths = tsconfig.compilerOptions.paths; - - const alias = Object.keys(paths).find((key) => { - const value = paths[key]; - - if (value.length !== 1) { - return false; - } - - const path = value[0]; - - if (usesSrcDir) { - return path === "./src/*"; - } else { - return path === "./*"; - } - }); - - // Make sure to remove the trailing "/*" - if (alias) { - return alias.slice(0, -2); - } - - return; -} - -async function createTriggerAppRoute( - projectPath: string, - path: string, - options: ResolvedOptions, - isTypescriptProject: boolean, - usesSrcDir = false -) { - const configFileName = isTypescriptProject ? "tsconfig.json" : "jsconfig.json"; - const tsConfigPath = pathModule.join(projectPath, configFileName); - const { tsconfig } = await parse(tsConfigPath); - - const extension = isTypescriptProject ? ".ts" : ".js"; - const triggerFileName = `trigger${extension}`; - const examplesFileName = `examples${extension}`; - const examplesIndexFileName = `index${extension}`; - const routeFileName = `route${extension}`; - - const pathAlias = getPathAlias(tsconfig, usesSrcDir); - const routePathPrefix = pathAlias ? pathAlias + "/" : "../../../"; - - const routeContent = ` -import { createAppRoute } from "@trigger.dev/nextjs"; -import { client } from "${routePathPrefix}trigger"; - - -import "${routePathPrefix}jobs"; - -//this route is used to send and receive data with Trigger.dev -export const { POST, dynamic } = createAppRoute(client); -`; - - const triggerContent = ` -import { TriggerClient } from "@trigger.dev/sdk"; - -export const client = new TriggerClient({ - id: "${options.endpointSlug}", - apiKey: process.env.TRIGGER_API_KEY, - apiUrl: process.env.TRIGGER_API_URL, -}); - `; - - const jobsPathPrefix = pathAlias ? pathAlias + "/" : "../"; - - const jobsContent = ` -import { eventTrigger } from "@trigger.dev/sdk"; -import { client } from "${jobsPathPrefix}trigger"; - -// Your first job -// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline -client.defineJob({ - // This is the unique identifier for your Job, it must be unique across all Jobs in your project - id: "example-job", - name: "Example Job: a joke with a delay", - version: "0.0.1", - // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction - trigger: eventTrigger({ - name: "example.event", - }), - run: async (payload, io, ctx) => { - // This logs a message to the console - await io.logger.info("πŸ§ͺ Example Job: a joke with a delay"); - await io.logger.info("How do you comfort a JavaScript bug?"); - // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year - await io.wait("Wait 5 seconds for the punchline...", 5); - await io.logger.info("You console it! 🀦"); - await io.logger.info( - "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" - ); - // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job - }, -}); - -`; - - const examplesIndexContent = ` -// import all your job files here - -export * from "./examples" -`; - - const directories = pathModule.join(path, "app", "api", "trigger"); - await fs.mkdir(directories, { recursive: true }); - - const fileExists = await pathExists(pathModule.join(directories, routeFileName)); - - if (fileExists) { - logger.info("Skipping creation of app route because it already exists"); - return; - } - - await fs.writeFile(pathModule.join(directories, routeFileName), routeContent); - - logger.success( - `βœ… Created app route at ${usesSrcDir ? "src/" : ""}app/api/${removeFileExtension( - triggerFileName - )}/${routeFileName}` - ); - - const triggerFileExists = await pathExists(pathModule.join(path, triggerFileName)); - - if (!triggerFileExists) { - await fs.writeFile(pathModule.join(path, triggerFileName), triggerContent); - - logger.success(`βœ… Created trigger client at ${usesSrcDir ? "src/" : ""}${triggerFileName}`); - } - - const exampleDirectories = pathModule.join(path, "jobs"); - await fs.mkdir(exampleDirectories, { recursive: true }); - - const exampleFileExists = await pathExists(pathModule.join(exampleDirectories, examplesFileName)); - - if (!exampleFileExists) { - await fs.writeFile(pathModule.join(exampleDirectories, examplesFileName), jobsContent); - - await fs.writeFile( - pathModule.join(exampleDirectories, examplesIndexFileName), - examplesIndexContent - ); - - logger.success(`βœ… Created example job at ${usesSrcDir ? "src/" : ""}jobs/examples.ts`); - } -} - -async function createTriggerPageRoute( - projectPath: string, - path: string, - options: ResolvedOptions, - isTypescriptProject: boolean, - usesSrcDir = false -) { - const configFileName = isTypescriptProject ? "tsconfig.json" : "jsconfig.json"; - const tsConfigPath = pathModule.join(projectPath, configFileName); - const { tsconfig } = await parse(tsConfigPath); - - const pathAlias = getPathAlias(tsconfig, usesSrcDir); - const routePathPrefix = pathAlias ? pathAlias + "/" : "../../"; - - const extension = isTypescriptProject ? ".ts" : ".js"; - const triggerFileName = `trigger${extension}`; - const examplesFileName = `examples${extension}`; - const examplesIndexFileName = `index${extension}`; - - const routeContent = ` -import { createPagesRoute } from "@trigger.dev/nextjs"; -import { client } from "${routePathPrefix}trigger"; - -import "${routePathPrefix}jobs"; - -//this route is used to send and receive data with Trigger.dev -const { handler, config } = createPagesRoute(client); -export { config }; - -export default handler; - `; - - const triggerContent = ` -import { TriggerClient } from "@trigger.dev/sdk"; - -export const client = new TriggerClient({ - id: "${options.endpointSlug}", - apiKey: process.env.TRIGGER_API_KEY, - apiUrl: process.env.TRIGGER_API_URL, -}); - `; - - const jobsPathPrefix = pathAlias ? pathAlias + "/" : "../"; - - const jobsContent = ` -import { eventTrigger } from "@trigger.dev/sdk"; -import { client } from "${jobsPathPrefix}trigger"; - -// Your first job -// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline -client.defineJob({ - // This is the unique identifier for your Job, it must be unique across all Jobs in your project - id: "example-job", - name: "Example Job: a joke with a delay", - version: "0.0.1", - // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction - trigger: eventTrigger({ - name: "example.event", - }), - run: async (payload, io, ctx) => { - // This logs a message to the console - await io.logger.info("πŸ§ͺ Example Job: a joke with a delay"); - await io.logger.info("How do you comfort a JavaScript bug?"); - // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year - await io.wait("Wait 5 seconds for the punchline...", 5); - await io.logger.info("You console it! 🀦"); - await io.logger.info( - "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" - ); - // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job - }, -}); -`; - - const examplesIndexContent = ` -// import all your job files here - -export * from "./examples" - `; - - const directories = pathModule.join(path, "pages", "api"); - await fs.mkdir(directories, { recursive: true }); - - // Don't overwrite the file if it already exists - const exists = await pathExists(pathModule.join(directories, triggerFileName)); - - if (exists) { - logger.info("Skipping creation of pages route because it already exists"); - return; - } - - await fs.writeFile(pathModule.join(directories, triggerFileName), routeContent); - logger.success( - `βœ… Created pages route at ${usesSrcDir ? "src/" : ""}pages/api/${triggerFileName}` - ); - - const triggerFileExists = await pathExists(pathModule.join(path, triggerFileName)); - - if (!triggerFileExists) { - await fs.writeFile(pathModule.join(path, triggerFileName), triggerContent); - - logger.success(`βœ… Created TriggerClient at ${usesSrcDir ? "src/" : ""}${triggerFileName}`); - } - - const exampleDirectories = pathModule.join(path, "jobs"); - await fs.mkdir(exampleDirectories, { recursive: true }); - - const exampleFileExists = await pathExists(pathModule.join(exampleDirectories, examplesFileName)); - - if (!exampleFileExists) { - await fs.writeFile(pathModule.join(exampleDirectories, examplesFileName), jobsContent); - - await fs.writeFile( - pathModule.join(exampleDirectories, examplesIndexFileName), - examplesIndexContent - ); - - logger.success( - `βœ… Created example job at ${usesSrcDir ? "src/" : ""}jobs/examples/${examplesFileName}` - ); - } -} - -async function setupEnvironmentVariables(path: string, options: ResolvedOptions) { - if (options.apiKey) { - await setupEnvironmentVariable( - path, - ".env.local", - "TRIGGER_API_KEY", - options.apiKey, - true, - renderApiKey - ); - } - - if (options.triggerUrl) { - await setupEnvironmentVariable(path, ".env.local", "TRIGGER_API_URL", options.triggerUrl, true); - } -} - -async function setupEnvironmentVariable( - dir: string, - fileName: string, - variableName: string, - value: string, - replaceValue: boolean = true, - renderer: (value: string) => string = (value) => value -) { - const path = pathModule.join(dir, fileName); - const envFileExists = await pathExists(path); - - if (!envFileExists) { - await fs.writeFile(path, ""); - } - - const envFileContent = await fs.readFile(path, "utf-8"); - - if (envFileContent.includes(variableName)) { - if (!replaceValue) { - logger.info( - `β˜‘ Skipping setting ${variableName}=${renderer(value)} because it already exists` - ); - return; - } - // Update the existing value - const updatedEnvFileContent = envFileContent.replace( - new RegExp(`${variableName}=.*\\n`, "g"), - `${variableName}=${value}\n` - ); - - await fs.writeFile(path, updatedEnvFileContent); - - logger.success(`βœ… Set ${variableName}=${renderer(value)} in ${fileName}`); - } else { - await fs.appendFile(path, `\n${variableName}=${value}`); - - logger.success(`βœ… Added ${variableName}=${renderer(value)} to ${fileName}`); - } -} - -function removeFileExtension(filename: string) { - return filename.replace(/\.[^.]+$/, ""); -} diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts index 73c0434fdb9..5d655ed8d02 100644 --- a/packages/cli/src/frameworks/index.ts +++ b/packages/cli/src/frameworks/index.ts @@ -2,12 +2,21 @@ import { InstallPackage } from "../utils/addDependencies"; import { PackageManager } from "../utils/getUserPkgManager"; import { NextJs } from "./nextjs"; +export type ProjectInstallOptions = { + typescript: boolean; + packageManager: PackageManager; + endpointSlug: string; +}; + export interface Framework { id: string; name: string; + defaultHostname: string; isMatch(path: string, packageManager: PackageManager): Promise; dependencies(): Promise; possibleEnvFilenames(): string[]; + install(path: string, options: ProjectInstallOptions): Promise; + postInstall(path: string, options: ProjectInstallOptions): Promise; } const frameworks: Framework[] = [new NextJs()]; diff --git a/packages/cli/src/frameworks/nextjs.ts b/packages/cli/src/frameworks/nextjs.ts deleted file mode 100644 index fe997750c50..00000000000 --- a/packages/cli/src/frameworks/nextjs.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Framework } from "."; -import { InstallPackage } from "../utils/addDependencies"; -import { PackageManager } from "../utils/getUserPkgManager"; -import fs from "fs/promises"; -import pathModule from "path"; -import { readPackageJson } from "../utils/readPackageJson"; - -export class NextJs implements Framework { - id = "nextjs"; - name = "Next.js"; - - async isMatch(path: string, packageManager: PackageManager): Promise { - const hasNextConfigFile = await detectNextConfigFile(path); - if (hasNextConfigFile) { - return true; - } - - return await detectNextDependency(path); - } - - async dependencies(): Promise { - return [ - { name: "@trigger.dev/sdk", tag: "latest" }, - { name: "@trigger.dev/nextjs", tag: "latest" }, - ]; - } - - possibleEnvFilenames(): string[] { - throw new Error("Method not implemented."); - } -} - -async function detectNextConfigFile(path: string): Promise { - return fs - .access(pathModule.join(path, "next.config.js")) - .then(() => true) - .catch(() => false); -} - -async function detectNextDependency(path: string): Promise { - const packageJsonContent = await readPackageJson(path); - if (!packageJsonContent) { - return false; - } - - return packageJsonContent.dependencies?.next !== undefined; -} diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts new file mode 100644 index 00000000000..47fb23bbc3c --- /dev/null +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -0,0 +1,415 @@ +import { pathToFileURL } from "url"; +import { Framework } from ".."; +import { InstallPackage } from "../../utils/addDependencies"; +import { PackageManager } from "../../utils/getUserPkgManager"; +import fs from "fs/promises"; +import pathModule from "path"; +import { readPackageJson } from "../../utils/readPackageJson"; +import { logger } from "../../utils/logger"; +import { pathExists } from "../../utils/fileSystem"; +import { parse } from "tsconfck"; +import { detectMiddlewareUsage } from "./middleware"; + +export class NextJs implements Framework { + id = "nextjs"; + name = "Next.js"; + defaultHostname = "localhost"; + + async isMatch(path: string, packageManager: PackageManager): Promise { + const hasNextConfigFile = await detectNextConfigFile(path); + if (hasNextConfigFile) { + return true; + } + + return await detectNextDependency(path); + } + + async dependencies(): Promise { + return [ + { name: "@trigger.dev/sdk", tag: "latest" }, + { name: "@trigger.dev/nextjs", tag: "latest" }, + ]; + } + + possibleEnvFilenames(): string[] { + return [".env.local", ".env"]; + } + + async install( + path: string, + options: { typescript: boolean; packageManager: PackageManager; endpointSlug: string } + ): Promise { + const usesSrcDir = await detectUseOfSrcDir(path); + if (usesSrcDir) { + logger.info("πŸ“ Detected use of src directory"); + } + + const nextJsDir = await detectPagesOrAppDir(path, usesSrcDir); + + const routeDir = pathModule.join(path, usesSrcDir ? "src" : ""); + + if (nextJsDir === "pages") { + await createTriggerPageRoute( + path, + routeDir, + options.endpointSlug, + options.typescript, + usesSrcDir + ); + } else { + await createTriggerAppRoute( + path, + routeDir, + options.endpointSlug, + options.typescript, + usesSrcDir + ); + } + } + + async postInstall( + path: string, + options: { typescript: boolean; packageManager: PackageManager; endpointSlug: string } + ): Promise { + await detectMiddlewareUsage(path); + } +} + +async function detectNextConfigFile(path: string): Promise { + return fs + .access(pathModule.join(path, "next.config.js")) + .then(() => true) + .catch(() => false); +} + +async function detectNextDependency(path: string): Promise { + const packageJsonContent = await readPackageJson(path); + if (!packageJsonContent) { + return false; + } + + return packageJsonContent.dependencies?.next !== undefined; +} + +async function detectUseOfSrcDir(path: string): Promise { + // Detects if the project is using a src directory + try { + await fs.access(pathModule.join(path, "src")); + return true; + } catch (error) { + return false; + } +} + +// Detect the use of pages or app dir in the Next.js project +// Import the next.config.js file and check for experimental: { appDir: true } +async function detectPagesOrAppDir(path: string, usesSrcDir = false): Promise<"pages" | "app"> { + const nextConfigPath = pathModule.join(path, "next.config.js"); + const importedConfig = await import(pathToFileURL(nextConfigPath).toString()).catch(() => ({})); + + if (importedConfig?.default?.experimental?.appDir) { + return "app"; + } + + // We need to check if src/app/page.tsx exists + // Or app/page.tsx exists + // If so then we return app + // If not return pages + + const extensionsToCheck = ["jsx", "tsx", "js", "ts"]; + const basePath = pathModule.join(path, usesSrcDir ? "src" : "", "app", `page.`); + + for (const extension of extensionsToCheck) { + const appPagePath = basePath + extension; + const appPageExists = await pathExists(appPagePath); + + if (appPageExists) { + return "app"; + } + } + + return "pages"; +} + +async function createTriggerPageRoute( + projectPath: string, + path: string, + endpointSlug: string, + isTypescriptProject: boolean, + usesSrcDir = false +) { + const configFileName = isTypescriptProject ? "tsconfig.json" : "jsconfig.json"; + const tsConfigPath = pathModule.join(projectPath, configFileName); + const { tsconfig } = await parse(tsConfigPath); + + const pathAlias = getPathAlias(tsconfig, usesSrcDir); + const routePathPrefix = pathAlias ? pathAlias + "/" : "../../"; + + const extension = isTypescriptProject ? ".ts" : ".js"; + const triggerFileName = `trigger${extension}`; + const examplesFileName = `examples${extension}`; + const examplesIndexFileName = `index${extension}`; + + const routeContent = ` +import { createPagesRoute } from "@trigger.dev/nextjs"; +import { client } from "${routePathPrefix}trigger"; + +import "${routePathPrefix}jobs"; + +//this route is used to send and receive data with Trigger.dev +const { handler, config } = createPagesRoute(client); +export { config }; + +export default handler; + `; + + const triggerContent = ` +import { TriggerClient } from "@trigger.dev/sdk"; + +export const client = new TriggerClient({ + id: "${endpointSlug}", + apiKey: process.env.TRIGGER_API_KEY, + apiUrl: process.env.TRIGGER_API_URL, +}); + `; + + const jobsPathPrefix = pathAlias ? pathAlias + "/" : "../"; + + const jobsContent = ` +import { eventTrigger } from "@trigger.dev/sdk"; +import { client } from "${jobsPathPrefix}trigger"; + +// Your first job +// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline +client.defineJob({ + // This is the unique identifier for your Job, it must be unique across all Jobs in your project + id: "example-job", + name: "Example Job: a joke with a delay", + version: "0.0.1", + // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + // This logs a message to the console + await io.logger.info("πŸ§ͺ Example Job: a joke with a delay"); + await io.logger.info("How do you comfort a JavaScript bug?"); + // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year + await io.wait("Wait 5 seconds for the punchline...", 5); + await io.logger.info("You console it! 🀦"); + await io.logger.info( + "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" + ); + // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job + }, +}); +`; + + const examplesIndexContent = ` +// import all your job files here + +export * from "./examples" + `; + + const directories = pathModule.join(path, "pages", "api"); + await fs.mkdir(directories, { recursive: true }); + + // Don't overwrite the file if it already exists + const exists = await pathExists(pathModule.join(directories, triggerFileName)); + + if (exists) { + logger.info("Skipping creation of pages route because it already exists"); + return; + } + + await fs.writeFile(pathModule.join(directories, triggerFileName), routeContent); + logger.success( + `βœ… Created pages route at ${usesSrcDir ? "src/" : ""}pages/api/${triggerFileName}` + ); + + const triggerFileExists = await pathExists(pathModule.join(path, triggerFileName)); + + if (!triggerFileExists) { + await fs.writeFile(pathModule.join(path, triggerFileName), triggerContent); + + logger.success(`βœ… Created TriggerClient at ${usesSrcDir ? "src/" : ""}${triggerFileName}`); + } + + const exampleDirectories = pathModule.join(path, "jobs"); + await fs.mkdir(exampleDirectories, { recursive: true }); + + const exampleFileExists = await pathExists(pathModule.join(exampleDirectories, examplesFileName)); + + if (!exampleFileExists) { + await fs.writeFile(pathModule.join(exampleDirectories, examplesFileName), jobsContent); + + await fs.writeFile( + pathModule.join(exampleDirectories, examplesIndexFileName), + examplesIndexContent + ); + + logger.success( + `βœ… Created example job at ${usesSrcDir ? "src/" : ""}jobs/examples/${examplesFileName}` + ); + } +} + +async function createTriggerAppRoute( + projectPath: string, + path: string, + endpointSlug: string, + isTypescriptProject: boolean, + usesSrcDir = false +) { + const configFileName = isTypescriptProject ? "tsconfig.json" : "jsconfig.json"; + const tsConfigPath = pathModule.join(projectPath, configFileName); + const { tsconfig } = await parse(tsConfigPath); + + const extension = isTypescriptProject ? ".ts" : ".js"; + const triggerFileName = `trigger${extension}`; + const examplesFileName = `examples${extension}`; + const examplesIndexFileName = `index${extension}`; + const routeFileName = `route${extension}`; + + const pathAlias = getPathAlias(tsconfig, usesSrcDir); + const routePathPrefix = pathAlias ? pathAlias + "/" : "../../../"; + + const routeContent = ` +import { createAppRoute } from "@trigger.dev/nextjs"; +import { client } from "${routePathPrefix}trigger"; + + +import "${routePathPrefix}jobs"; + +//this route is used to send and receive data with Trigger.dev +export const { POST, dynamic } = createAppRoute(client); +`; + + const triggerContent = ` +import { TriggerClient } from "@trigger.dev/sdk"; + +export const client = new TriggerClient({ + id: "${endpointSlug}", + apiKey: process.env.TRIGGER_API_KEY, + apiUrl: process.env.TRIGGER_API_URL, +}); + `; + + const jobsPathPrefix = pathAlias ? pathAlias + "/" : "../"; + + const jobsContent = ` +import { eventTrigger } from "@trigger.dev/sdk"; +import { client } from "${jobsPathPrefix}trigger"; + +// Your first job +// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline +client.defineJob({ + // This is the unique identifier for your Job, it must be unique across all Jobs in your project + id: "example-job", + name: "Example Job: a joke with a delay", + version: "0.0.1", + // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + // This logs a message to the console + await io.logger.info("πŸ§ͺ Example Job: a joke with a delay"); + await io.logger.info("How do you comfort a JavaScript bug?"); + // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year + await io.wait("Wait 5 seconds for the punchline...", 5); + await io.logger.info("You console it! 🀦"); + await io.logger.info( + "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" + ); + // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job + }, +}); + +`; + + const examplesIndexContent = ` +// import all your job files here + +export * from "./examples" +`; + + const directories = pathModule.join(path, "app", "api", "trigger"); + await fs.mkdir(directories, { recursive: true }); + + const fileExists = await pathExists(pathModule.join(directories, routeFileName)); + + if (fileExists) { + logger.info("Skipping creation of app route because it already exists"); + return; + } + + await fs.writeFile(pathModule.join(directories, routeFileName), routeContent); + + logger.success( + `βœ… Created app route at ${usesSrcDir ? "src/" : ""}app/api/${removeFileExtension( + triggerFileName + )}/${routeFileName}` + ); + + const triggerFileExists = await pathExists(pathModule.join(path, triggerFileName)); + + if (!triggerFileExists) { + await fs.writeFile(pathModule.join(path, triggerFileName), triggerContent); + + logger.success(`βœ… Created trigger client at ${usesSrcDir ? "src/" : ""}${triggerFileName}`); + } + + const exampleDirectories = pathModule.join(path, "jobs"); + await fs.mkdir(exampleDirectories, { recursive: true }); + + const exampleFileExists = await pathExists(pathModule.join(exampleDirectories, examplesFileName)); + + if (!exampleFileExists) { + await fs.writeFile(pathModule.join(exampleDirectories, examplesFileName), jobsContent); + + await fs.writeFile( + pathModule.join(exampleDirectories, examplesIndexFileName), + examplesIndexContent + ); + + logger.success(`βœ… Created example job at ${usesSrcDir ? "src/" : ""}jobs/examples.ts`); + } +} + +// Find the alias that points to the "src" directory. +// So for example, the paths object could be: +// { +// "@/*": ["./src/*"] +// } +// In this case, we would return "@" +function getPathAlias(tsconfig: any, usesSrcDir: boolean) { + if (!tsconfig.compilerOptions.paths) { + return; + } + + const paths = tsconfig.compilerOptions.paths; + + const alias = Object.keys(paths).find((key) => { + const value = paths[key]; + + if (value.length !== 1) { + return false; + } + + const path = value[0]; + + if (usesSrcDir) { + return path === "./src/*"; + } else { + return path === "./*"; + } + }); + + // Make sure to remove the trailing "/*" + if (alias) { + return alias.slice(0, -2); + } + + return; +} diff --git a/packages/cli/src/frameworks/nextjs/middleware.ts b/packages/cli/src/frameworks/nextjs/middleware.ts new file mode 100644 index 00000000000..9bf1fa20d88 --- /dev/null +++ b/packages/cli/src/frameworks/nextjs/middleware.ts @@ -0,0 +1,93 @@ +import fs from "fs/promises"; +import { pathExists } from "../../utils/fileSystem"; +import pathModule from "path"; +import { logger } from "../../utils/logger"; +import { telemetryClient } from "../../telemetry/telemetry"; +import { pathToRegexp } from "path-to-regexp"; + +export async function detectMiddlewareUsage(path: string, usesSrcDir = false) { + const middlewarePath = pathModule.join(path, usesSrcDir ? "src" : "", "middleware.ts"); + + const middlewareExists = await pathExists(middlewarePath); + + if (!middlewareExists) { + return; + } + + const matcher = await getMiddlewareConfigMatcher(middlewarePath); + + if (!matcher || matcher.length === 0) { + logger.warn( + `⚠️ ⚠️ ⚠️ It looks like there might be conflicting Next.js middleware in ${pathModule.relative( + process.cwd(), + middlewarePath + )} 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; + } + + if (matcher.length === 0) { + return; + } + + if (typeof matcher === "string") { + const matcherRegex = pathToRegexp(matcher); + + // 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 there might be conflicting Next.js middleware in ${pathModule.relative( + process.cwd(), + 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)); + + if (matcherRegexes.some((r) => r.test("/api/trigger"))) { + logger.warn( + `🚨 It looks like there might be conflicting Next.js middleware in ${pathModule.relative( + process.cwd(), + 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 }); + } + } +} + +async function getMiddlewareConfigMatcher(path: string): Promise> { + const fileContent = await fs.readFile(path, "utf-8"); + + const regex = /matcher:\s*(\[.*\]|".*")/s; + let match = regex.exec(fileContent); + + if (!match) { + return []; + } + + if (match.length < 2) { + return []; + } + + let matcherString: string = match[1] as string; + + // Handle array scenario + if (matcherString.startsWith("[") && matcherString.endsWith("]")) { + matcherString = matcherString.slice(1, -1); // Remove brackets + const arrayRegex = /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/g; + let arrayMatch; + const matches: string[] = []; + while ((arrayMatch = arrayRegex.exec(matcherString)) !== null) { + matches.push((arrayMatch[1] as string).slice(1, -1)); // remove quotes + } + return matches; + } else { + // Handle single string scenario + return [matcherString.slice(1, -1)]; // remove quotes + } +} diff --git a/packages/cli/src/telemetry/telemetry.ts b/packages/cli/src/telemetry/telemetry.ts index fd2f3e6603c..6ff8d5bc80e 100644 --- a/packages/cli/src/telemetry/telemetry.ts +++ b/packages/cli/src/telemetry/telemetry.ts @@ -3,6 +3,7 @@ import { InitCommandOptions } from "../commands/init"; import { nanoid } from "nanoid"; import { getVersion } from "../utils/getVersion"; import { DevCommandOptions } from "../commands/dev"; +import { ProjectInstallOptions } from "../frameworks"; const postHogApiKey = "phc_hwYmedO564b3Ik8nhA4Csrb5SueY0EwFJWCbseGwWW"; @@ -82,11 +83,26 @@ export class TelemetryClient { properties: this.#initProperties(options), }); }, - createFiles: (options: InitCommandOptions, routerType: "pages" | "app") => { + install: ( + options: InitCommandOptions, + framework: string, + installOptions: ProjectInstallOptions + ) => { + this.#client.capture({ + distinctId: this.#sessionId, + event: "cli_init_install", + properties: { ...this.#initProperties(options), framework, ...installOptions }, + }); + }, + postInstall: ( + options: InitCommandOptions, + framework: string, + installOptions: ProjectInstallOptions + ) => { this.#client.capture({ distinctId: this.#sessionId, - event: "cli_init_create_files", - properties: { ...this.#initProperties(options), routerType }, + event: "cli_init_post_install", + properties: { ...this.#initProperties(options), framework, ...installOptions }, }); }, completed: (options: InitCommandOptions) => { diff --git a/packages/cli/src/utils/addDependencies.ts b/packages/cli/src/utils/addDependencies.ts index 500c183c717..2e9f3ce0def 100644 --- a/packages/cli/src/utils/addDependencies.ts +++ b/packages/cli/src/utils/addDependencies.ts @@ -10,8 +10,6 @@ import { z } from "zod"; export type InstallPackage = { name: string; tag: string; - /** Defaults to prod */ - type?: "dev" | "prod"; }; export type InstalledPackage = { diff --git a/packages/cli/src/utils/env.ts b/packages/cli/src/utils/env.ts new file mode 100644 index 00000000000..ec0b55dbf1b --- /dev/null +++ b/packages/cli/src/utils/env.ts @@ -0,0 +1,75 @@ +import fs from "fs/promises"; +import pathModule from "path"; +import { pathExists } from "./fileSystem"; +import { logger } from "./logger"; +import { renderApiKey } from "./renderApiKey"; + +export async function getEnvFilename( + directory: string, + possibleNames: string[] +): Promise { + if (possibleNames.length === 0) { + throw new Error("No possible names provided"); + } + + for (let index = 0; index < possibleNames.length; index++) { + const name = possibleNames[index]; + if (!name) continue; + + const path = pathModule.join(directory, name); + const envFileExists = await pathExists(path); + if (envFileExists) { + return name; + } + } + + return undefined; +} + +export async function setApiKeyEnvironmentVariable(dir: string, fileName: string, apiKey: string) { + await setEnvironmentVariable(dir, fileName, "TRIGGER_API_KEY", apiKey, true, renderApiKey); +} + +export async function setApiUrlEnvironmentVariable(dir: string, fileName: string, apiUrl: string) { + await setEnvironmentVariable(dir, fileName, "TRIGGER_API_URL", apiUrl, true); +} + +async function setEnvironmentVariable( + dir: string, + fileName: string, + variableName: string, + value: string, + replaceValue: boolean = true, + renderer: (value: string) => string = (value) => value +) { + const path = pathModule.join(dir, fileName); + const envFileExists = await pathExists(path); + + if (!envFileExists) { + await fs.writeFile(path, ""); + } + + const envFileContent = await fs.readFile(path, "utf-8"); + + if (envFileContent.includes(variableName)) { + if (!replaceValue) { + logger.info( + `β˜‘ Skipping setting ${variableName}=${renderer(value)} because it already exists` + ); + return; + } + // Update the existing value + const updatedEnvFileContent = envFileContent.replace( + new RegExp(`${variableName}=.*\\n`, "g"), + `${variableName}=${value}\n` + ); + + await fs.writeFile(path, updatedEnvFileContent); + + logger.success(`βœ… Set ${variableName}=${renderer(value)} in ${fileName}`); + } else { + await fs.appendFile(path, `\n${variableName}=${value}`); + + logger.success(`βœ… Added ${variableName}=${renderer(value)} to ${fileName}`); + } +} diff --git a/packages/cli/src/utils/removeFileExtension.ts b/packages/cli/src/utils/removeFileExtension.ts new file mode 100644 index 00000000000..edcef8df253 --- /dev/null +++ b/packages/cli/src/utils/removeFileExtension.ts @@ -0,0 +1,3 @@ +function removeFileExtension(filename: string) { + return filename.replace(/\.[^.]+$/, ""); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e32ccda19e..945273c245b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -465,6 +465,37 @@ importers: '@trigger.dev/cli': link:../../packages/cli concurrently: 8.2.0 + examples/my-app: + specifiers: + '@trigger.dev/cli': workspace:* + '@types/node': 20.6.0 + '@types/react': 18.2.21 + '@types/react-dom': 18.2.7 + autoprefixer: 10.4.15 + eslint: 8.49.0 + eslint-config-next: 13.4.19 + next: 13.4.19 + postcss: 8.4.29 + react: 18.2.0 + react-dom: 18.2.0 + tailwindcss: 3.3.3 + typescript: 5.2.2 + dependencies: + '@types/node': 20.6.0 + '@types/react': 18.2.21 + '@types/react-dom': 18.2.7 + autoprefixer: 10.4.15_postcss@8.4.29 + eslint: 8.49.0 + eslint-config-next: 13.4.19_rngtr6f3b25lvetpihwplgecf4 + next: 13.4.19_biqbaboplfbrettd7655fr4n2y + postcss: 8.4.29 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + tailwindcss: 3.3.3 + typescript: 5.2.2 + devDependencies: + '@trigger.dev/cli': link:../../packages/cli + examples/nextjs-12: specifiers: '@trigger.dev/cli': workspace:* @@ -689,8 +720,8 @@ importers: integrations/airtable: specifiers: - '@trigger.dev/integration-kit': workspace:^2.1.2 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/integration-kit': workspace:^2.1.3 + '@trigger.dev/sdk': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x airtable: ^0.12.1 @@ -717,8 +748,8 @@ importers: '@octokit/types': ^9.2.3 '@octokit/webhooks': ^10.4.0 '@octokit/webhooks-types': ^6.10.0 - '@trigger.dev/integration-kit': workspace:^2.1.2 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/integration-kit': workspace:^2.1.3 + '@trigger.dev/sdk': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' octokit: ^2.0.14 @@ -743,8 +774,8 @@ importers: integrations/openai: specifiers: - '@trigger.dev/integration-kit': workspace:^2.1.2 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/integration-kit': workspace:^2.1.3 + '@trigger.dev/sdk': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' openai: ^4.2.0 @@ -763,8 +794,8 @@ importers: integrations/plain: specifiers: '@team-plain/typescript-sdk': ^2.7.0 - '@trigger.dev/integration-kit': workspace:^2.1.2 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/integration-kit': workspace:^2.1.3 + '@trigger.dev/sdk': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' rimraf: ^3.0.2 @@ -781,8 +812,8 @@ importers: integrations/resend: specifiers: - '@trigger.dev/integration-kit': workspace:^2.1.2 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/integration-kit': workspace:^2.1.3 + '@trigger.dev/sdk': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' resend: ^1.0.0 @@ -801,8 +832,8 @@ importers: integrations/sendgrid: specifiers: '@sendgrid/mail': ^7.7.0 - '@trigger.dev/integration-kit': workspace:^2.1.2 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/integration-kit': workspace:^2.1.3 + '@trigger.dev/sdk': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x rimraf: ^3.0.2 @@ -822,7 +853,7 @@ importers: integrations/slack: specifiers: '@slack/web-api': ^6.8.1 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/sdk': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' rimraf: ^3.0.2 @@ -840,8 +871,8 @@ importers: integrations/stripe: specifiers: - '@trigger.dev/integration-kit': workspace:^2.1.2 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/integration-kit': workspace:^2.1.3 + '@trigger.dev/sdk': workspace:^2.1.3 '@types/node': 16.x rimraf: ^3.0.2 stripe: ^12.14.0 @@ -864,8 +895,8 @@ importers: integrations/supabase: specifiers: '@supabase/supabase-js': ^2.26.0 - '@trigger.dev/integration-kit': workspace:^2.1.2 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/integration-kit': workspace:^2.1.3 + '@trigger.dev/sdk': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/node': 18.x rimraf: ^3.0.2 @@ -888,8 +919,8 @@ importers: integrations/typeform: specifiers: - '@trigger.dev/integration-kit': workspace:^2.1.2 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/integration-kit': workspace:^2.1.3 + '@trigger.dev/sdk': workspace:^2.1.3 '@typeform/api-client': ^1.8.0 '@types/node': 16.x rimraf: ^3.0.2 @@ -1116,7 +1147,7 @@ importers: packages/express: specifiers: '@remix-run/web-fetch': ^4.3.5 - '@trigger.dev/sdk': workspace:^2.1.2 + '@trigger.dev/sdk': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/express': ^4.17.13 @@ -1189,7 +1220,7 @@ importers: packages/react: specifiers: '@tanstack/react-query': 5.0.0-beta.2 - '@trigger.dev/core': workspace:^2.1.2 + '@trigger.dev/core': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/react': 18.2.17 @@ -1265,7 +1296,7 @@ importers: packages/trigger-sdk: specifiers: - '@trigger.dev/core': workspace:^2.1.2 + '@trigger.dev/core': workspace:^2.1.3 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/node': '18' @@ -5897,10 +5928,25 @@ packages: eslint: 8.45.0 eslint-visitor-keys: 3.4.2 + /@eslint-community/eslint-utils/4.4.0_eslint@8.49.0: + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.49.0 + eslint-visitor-keys: 3.4.2 + dev: false + /@eslint-community/regexpp/4.5.1: resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + /@eslint-community/regexpp/4.8.1: + resolution: {integrity: sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: false + /@eslint/eslintrc/1.4.1: resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5934,6 +5980,23 @@ packages: transitivePeerDependencies: - supports-color + /@eslint/eslintrc/2.1.2: + resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.19.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: false + /@eslint/js/8.42.0: resolution: {integrity: sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5943,6 +6006,11 @@ packages: resolution: {integrity: sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@eslint/js/8.49.0: + resolution: {integrity: sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: false + /@fal-works/esbuild-plugin-global-externals/2.1.2: resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} dev: true @@ -6179,6 +6247,17 @@ packages: transitivePeerDependencies: - supports-color + /@humanwhocodes/config-array/0.11.11: + resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: false + /@humanwhocodes/config-array/0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} @@ -6695,6 +6774,10 @@ packages: resolution: {integrity: sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==} dev: false + /@next/env/13.4.19: + resolution: {integrity: sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==} + dev: false + /@next/eslint-plugin-next/12.3.4: resolution: {integrity: sha512-BFwj8ykJY+zc1/jWANsDprDIu2MgwPOIKxNVnrKvPs+f5TPegrVnem8uScND+1veT4B7F6VeqgaNLFW1Hzl9Og==} dependencies: @@ -6713,6 +6796,12 @@ packages: glob: 7.1.7 dev: false + /@next/eslint-plugin-next/13.4.19: + resolution: {integrity: sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==} + dependencies: + glob: 7.1.7 + dev: false + /@next/eslint-plugin-next/13.4.6: resolution: {integrity: sha512-bPigeu0RI7bgy1ucBA2Yqcfg539y0Lzo38P2hIkrRB1GNvFSbYg6RTu8n6tGqPVrH3TTlPTNKLXG01wc+5NuwQ==} dependencies: @@ -6772,6 +6861,15 @@ packages: dev: false optional: true + /@next/swc-darwin-arm64/13.4.19: + resolution: {integrity: sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-darwin-x64/12.3.4: resolution: {integrity: sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==} engines: {node: '>= 10'} @@ -6807,6 +6905,15 @@ packages: dev: false optional: true + /@next/swc-darwin-x64/13.4.19: + resolution: {integrity: sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-freebsd-x64/12.3.4: resolution: {integrity: sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==} engines: {node: '>= 10'} @@ -6860,6 +6967,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-gnu/13.4.19: + resolution: {integrity: sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-musl/12.3.4: resolution: {integrity: sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==} engines: {node: '>= 10'} @@ -6895,6 +7011,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-musl/13.4.19: + resolution: {integrity: sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-gnu/12.3.4: resolution: {integrity: sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==} engines: {node: '>= 10'} @@ -6930,6 +7055,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-gnu/13.4.19: + resolution: {integrity: sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-musl/12.3.4: resolution: {integrity: sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==} engines: {node: '>= 10'} @@ -6965,6 +7099,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-musl/13.4.19: + resolution: {integrity: sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-arm64-msvc/12.3.4: resolution: {integrity: sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==} engines: {node: '>= 10'} @@ -7000,6 +7143,15 @@ packages: dev: false optional: true + /@next/swc-win32-arm64-msvc/13.4.19: + resolution: {integrity: sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-ia32-msvc/12.3.4: resolution: {integrity: sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==} engines: {node: '>= 10'} @@ -7035,6 +7187,15 @@ packages: dev: false optional: true + /@next/swc-win32-ia32-msvc/13.4.19: + resolution: {integrity: sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-x64-msvc/12.3.4: resolution: {integrity: sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==} engines: {node: '>= 10'} @@ -7070,6 +7231,15 @@ packages: dev: false optional: true + /@next/swc-win32-x64-msvc/13.4.19: + resolution: {integrity: sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@nicolo-ribaudo/eslint-scope-5-internals/5.1.1-v1: resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} dependencies: @@ -12476,7 +12646,7 @@ packages: /@types/react-dom/18.2.7: resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} dependencies: - '@types/react': 18.2.17 + '@types/react': 18.2.21 /@types/react/18.2.17: resolution: {integrity: sha512-u+e7OlgPPh+aryjOm5UJMX32OvB2E3QASOAqVMY6Ahs90djagxwv2ya0IctglNbNTexC12qCSMZG47KPfy1hAA==} @@ -12485,6 +12655,13 @@ packages: '@types/scheduler': 0.16.2 csstype: 3.1.1 + /@types/react/18.2.21: + resolution: {integrity: sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.1 + /@types/resolve/1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -12781,6 +12958,26 @@ packages: - supports-color dev: true + /@typescript-eslint/parser/5.59.6_rngtr6f3b25lvetpihwplgecf4: + resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.59.6 + '@typescript-eslint/types': 5.59.6 + '@typescript-eslint/typescript-estree': 5.59.6_typescript@5.2.2 + debug: 4.3.4 + eslint: 8.49.0 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: false + /@typescript-eslint/scope-manager/5.59.6: resolution: {integrity: sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -12935,6 +13132,27 @@ packages: transitivePeerDependencies: - supports-color + /@typescript-eslint/typescript-estree/5.59.6_typescript@5.2.2: + resolution: {integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.59.6 + '@typescript-eslint/visitor-keys': 5.59.6 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + tsutils: 3.21.0_typescript@5.2.2 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: false + /@typescript-eslint/utils/5.59.6_eslint@8.45.0: resolution: {integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -13000,7 +13218,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.2 + eslint-visitor-keys: 3.4.3 /@uiw/codemirror-extensions-basic-setup/4.19.5_wd2tsis3in55bkaiwnc2c46tom: resolution: {integrity: sha512-1zt7ZPJ01xKkSW/KDy0FZNga0bngN1fC594wCVG7FBi60ehfcAucpooQ+JSPScKXopxcb+ugPKZvVLzr9/OfzA==} @@ -13978,6 +14196,22 @@ packages: postcss-value-parser: 4.2.0 dev: false + /autoprefixer/10.4.15_postcss@8.4.29: + resolution: {integrity: sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.10 + caniuse-lite: 1.0.30001532 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.29 + postcss-value-parser: 4.2.0 + dev: false + /available-typed-arrays/1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} @@ -16906,6 +17140,31 @@ packages: - supports-color dev: false + /eslint-config-next/13.4.19_rngtr6f3b25lvetpihwplgecf4: + resolution: {integrity: sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@next/eslint-plugin-next': 13.4.19 + '@rushstack/eslint-patch': 1.2.0 + '@typescript-eslint/parser': 5.59.6_rngtr6f3b25lvetpihwplgecf4 + eslint: 8.49.0 + eslint-import-resolver-node: 0.3.7 + eslint-import-resolver-typescript: 3.5.5_3gbtcjnbjinsubg5cwv3525lbq + eslint-plugin-import: 2.27.5_tyljvsdz2ipp3eruvtbrjckloe + eslint-plugin-jsx-a11y: 6.7.1_eslint@8.49.0 + eslint-plugin-react: 7.32.2_eslint@8.49.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.49.0 + typescript: 5.2.2 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - supports-color + dev: false + /eslint-config-next/13.4.6_binxsscxvozjxebftqdoazsxm4: resolution: {integrity: sha512-nlv4FYish1RYYHILbQwM5/rD37cOvEqtMfDjtQCYbXdE2O3MggqHu2q6IDeLE2Z6u8ZJyNPgWOA6OimWcxj3qw==} peerDependencies: @@ -17002,8 +17261,8 @@ packages: resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} dependencies: debug: 3.2.7 - is-core-module: 2.11.0 - resolve: 1.22.2 + is-core-module: 2.13.0 + resolve: 1.22.4 transitivePeerDependencies: - supports-color @@ -17025,6 +17284,30 @@ packages: - supports-color dev: true + /eslint-import-resolver-typescript/3.5.5_3gbtcjnbjinsubg5cwv3525lbq: + resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + dependencies: + debug: 4.3.4 + enhanced-resolve: 5.13.0 + eslint: 8.49.0 + eslint-module-utils: 2.7.4_bytowunmwsvn7w25lnp5m6zpem + eslint-plugin-import: 2.27.5_tyljvsdz2ipp3eruvtbrjckloe + get-tsconfig: 4.5.0 + globby: 13.1.3 + is-core-module: 2.11.0 + is-glob: 4.0.3 + synckit: 0.8.5 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + dev: false + /eslint-import-resolver-typescript/3.5.5_bsgoee2ktd7nzirwexa3gasa7m: resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -17126,6 +17409,36 @@ packages: - supports-color dev: true + /eslint-module-utils/2.7.4_bytowunmwsvn7w25lnp5m6zpem: + resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.6_rngtr6f3b25lvetpihwplgecf4 + debug: 3.2.7 + eslint: 8.49.0 + eslint-import-resolver-node: 0.3.7 + eslint-import-resolver-typescript: 3.5.5_3gbtcjnbjinsubg5cwv3525lbq + transitivePeerDependencies: + - supports-color + dev: false + /eslint-module-utils/2.7.4_hhwxwo5vg2mzr5fq3eei7kqaye: resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} engines: {node: '>=4'} @@ -17347,6 +17660,39 @@ packages: - supports-color dev: true + /eslint-plugin-import/2.27.5_tyljvsdz2ipp3eruvtbrjckloe: + resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.6_rngtr6f3b25lvetpihwplgecf4 + array-includes: 3.1.6 + array.prototype.flat: 1.3.1 + array.prototype.flatmap: 1.3.1 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.49.0 + eslint-import-resolver-node: 0.3.7 + eslint-module-utils: 2.7.4_bytowunmwsvn7w25lnp5m6zpem + has: 1.0.3 + is-core-module: 2.11.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.6 + resolve: 1.22.2 + semver: 6.3.0 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: false + /eslint-plugin-import/2.27.5_xpt3ce3kmhzhmuk3dcuwe6u2pe: resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} @@ -17544,6 +17890,31 @@ packages: object.fromentries: 2.0.6 semver: 6.3.0 + /eslint-plugin-jsx-a11y/6.7.1_eslint@8.49.0: + resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + '@babel/runtime': 7.22.5 + aria-query: 5.1.3 + array-includes: 3.1.6 + array.prototype.flatmap: 1.3.1 + ast-types-flow: 0.0.7 + axe-core: 4.6.2 + axobject-query: 3.1.1 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 8.49.0 + has: 1.0.3 + jsx-ast-utils: 3.3.3 + language-tags: 1.0.5 + minimatch: 3.1.2 + object.entries: 1.1.6 + object.fromentries: 2.0.6 + semver: 6.3.0 + dev: false + /eslint-plugin-node/11.1.0_eslint@8.31.0: resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} engines: {node: '>=8.10.0'} @@ -17610,6 +17981,15 @@ packages: eslint: 8.45.0 dev: true + /eslint-plugin-react-hooks/4.6.0_eslint@8.49.0: + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.49.0 + dev: false + /eslint-plugin-react-hooks/5.0.0-canary-7118f5dd7-20230705_eslint@8.45.0: resolution: {integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==} engines: {node: '>=10'} @@ -17737,6 +18117,30 @@ packages: semver: 6.3.0 string.prototype.matchall: 4.0.8 + /eslint-plugin-react/7.32.2_eslint@8.49.0: + resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + array-includes: 3.1.6 + array.prototype.flatmap: 1.3.1 + array.prototype.tosorted: 1.1.1 + doctrine: 2.1.0 + eslint: 8.49.0 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.3 + minimatch: 3.1.2 + object.entries: 1.1.6 + object.fromentries: 2.0.6 + object.hasown: 1.1.2 + object.values: 1.1.6 + prop-types: 15.8.1 + resolve: 2.0.0-next.4 + semver: 6.3.0 + string.prototype.matchall: 4.0.8 + dev: false + /eslint-plugin-testing-library/5.11.0_iukboom6ndih5an6iafl45j2fe: resolution: {integrity: sha512-ELY7Gefo+61OfXKlQeXNIDVVLPcvKTeiQOoMZG9TeuWa7Ln4dUNRv8JdRWBQI9Mbb427XGlVB1aa1QPZxBJM8Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} @@ -17795,6 +18199,14 @@ packages: esrecurse: 4.3.0 estraverse: 5.3.0 + /eslint-scope/7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: false + /eslint-utils/2.1.0: resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} engines: {node: '>=6'} @@ -17846,6 +18258,10 @@ packages: resolution: {integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /eslint-visitor-keys/3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + 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} @@ -18035,6 +18451,52 @@ packages: transitivePeerDependencies: - supports-color + /eslint/8.49.0: + resolution: {integrity: sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0_eslint@8.49.0 + '@eslint-community/regexpp': 4.8.1 + '@eslint/eslintrc': 2.1.2 + '@eslint/js': 8.49.0 + '@humanwhocodes/config-array': 0.11.11 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.19.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: false + /espree/9.4.1: resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -18052,6 +18514,15 @@ packages: acorn-jsx: 5.3.2_acorn@8.10.0 eslint-visitor-keys: 3.4.2 + /espree/9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.10.0 + acorn-jsx: 5.3.2_acorn@8.10.0 + eslint-visitor-keys: 3.4.3 + dev: false + /esprima/4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -23133,6 +23604,46 @@ packages: - babel-plugin-macros dev: false + /next/13.4.19_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==} + engines: {node: '>=16.8.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + sass: + optional: true + dependencies: + '@next/env': 13.4.19 + '@swc/helpers': 0.5.1 + busboy: 1.6.0 + caniuse-lite: 1.0.30001532 + postcss: 8.4.14 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + styled-jsx: 5.1.1_react@18.2.0 + watchpack: 2.4.0 + zod: 3.21.4 + optionalDependencies: + '@next/swc-darwin-arm64': 13.4.19 + '@next/swc-darwin-x64': 13.4.19 + '@next/swc-linux-arm64-gnu': 13.4.19 + '@next/swc-linux-arm64-musl': 13.4.19 + '@next/swc-linux-x64-gnu': 13.4.19 + '@next/swc-linux-x64-musl': 13.4.19 + '@next/swc-win32-arm64-msvc': 13.4.19 + '@next/swc-win32-ia32-msvc': 13.4.19 + '@next/swc-win32-x64-msvc': 13.4.19 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /ngrok/5.0.0-beta.2: resolution: {integrity: sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ==} engines: {node: '>=14.2'} @@ -26096,7 +26607,7 @@ packages: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} hasBin: true dependencies: - is-core-module: 2.11.0 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -27500,7 +28011,7 @@ packages: chokidar: 3.5.3 didyoumean: 1.2.2 dlv: 1.1.3 - fast-glob: 3.2.12 + fast-glob: 3.3.1 glob-parent: 6.0.2 is-glob: 4.0.3 jiti: 1.18.2 @@ -27515,7 +28026,7 @@ packages: postcss-load-config: 4.0.1_postcss@8.4.29 postcss-nested: 6.0.1_postcss@8.4.29 postcss-selector-parser: 6.0.11 - resolve: 1.22.2 + resolve: 1.22.4 sucrase: 3.32.0 transitivePeerDependencies: - ts-node @@ -28329,6 +28840,16 @@ packages: tslib: 1.14.1 typescript: 5.1.6 + /tsutils/3.21.0_typescript@5.2.2: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 5.2.2 + dev: false + /tsx/3.12.2: resolution: {integrity: sha512-ykAEkoBg30RXxeOMVeZwar+JH632dZn9EUJVyJwhfag62k6UO/dIyJEV58YuLF6e5BTdV/qmbQrpkWqjq9cUnQ==} hasBin: true @@ -28550,7 +29071,6 @@ packages: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} hasBin: true - dev: true /ufo/1.3.0: resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} From 1d9a6dc1bde815c7bd50b002b916b3d21c5dd7bc Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 14 Sep 2023 18:01:06 +0100 Subject: [PATCH 04/53] Some fixes --- packages/cli/src/commands/init.ts | 5 +++-- packages/cli/src/frameworks/nextjs/index.ts | 1 + packages/cli/src/utils/removeFileExtension.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 185139c487c..0b7166886a0 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -111,8 +111,9 @@ export const initCommand = async (options: InitCommandOptions) => { // Setup environment variables (create a file if there isn't one) let envName = await getEnvFilename(resolvedPath, framework.possibleEnvFilenames()); if (!envName) { - envName = pathModule.join(resolvedPath, framework.possibleEnvFilenames()[0]!); - await fs.writeFile(envName, ""); + envName = framework.possibleEnvFilenames()[0]!; + const newEnvPath = pathModule.join(resolvedPath, framework.possibleEnvFilenames()[0]!); + await fs.writeFile(newEnvPath, ""); } await setApiKeyEnvironmentVariable(resolvedPath, envName, resolvedOptions.apiKey); await setApiUrlEnvironmentVariable(resolvedPath, envName, resolvedOptions.apiUrl); diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 47fb23bbc3c..cf4e52f7e8f 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -9,6 +9,7 @@ import { logger } from "../../utils/logger"; import { pathExists } from "../../utils/fileSystem"; import { parse } from "tsconfck"; import { detectMiddlewareUsage } from "./middleware"; +import { removeFileExtension } from "../../utils/removeFileExtension"; export class NextJs implements Framework { id = "nextjs"; diff --git a/packages/cli/src/utils/removeFileExtension.ts b/packages/cli/src/utils/removeFileExtension.ts index edcef8df253..77f771c1b40 100644 --- a/packages/cli/src/utils/removeFileExtension.ts +++ b/packages/cli/src/utils/removeFileExtension.ts @@ -1,3 +1,3 @@ -function removeFileExtension(filename: string) { +export function removeFileExtension(filename: string) { return filename.replace(/\.[^.]+$/, ""); } From c66ddab8ebcfa083b8d646799814f5c40104b573 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 12:42:11 +0100 Subject: [PATCH 05/53] WIP creating unit tests for Next.js project detection --- packages/cli/package.json | 4 +++- packages/cli/src/frameworks/nextjs/index.ts | 2 +- .../cli/src/frameworks/nextjs/install.test.ts | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 packages/cli/src/frameworks/nextjs/install.test.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index f33c78a0579..cb1ad5304d8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -40,9 +40,11 @@ "@types/gradient-string": "^1.1.2", "@types/inquirer": "^9.0.3", "@types/jest": "^29.5.3", + "@types/mock-fs": "^4.13.1", "@types/node": "16", "@types/node-fetch": "^2.6.2", "jest": "^29.6.2", + "mock-fs": "^5.2.0", "rimraf": "^3.0.2", "ts-jest": "^29.1.1", "tsup": "^6.5.0", @@ -68,11 +70,11 @@ "gradient-string": "^2.0.2", "inquirer": "^9.1.4", "localtunnel": "^2.0.2", - "openai": "^4.5.0", "nanoid": "^4.0.2", "ngrok": "5.0.0-beta.2", "node-fetch": "^3.3.0", "npm-check-updates": "^16.12.2", + "openai": "^4.5.0", "ora": "^6.1.2", "path-to-regexp": "^6.2.1", "posthog-node": "^3.1.1", diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index cf4e52f7e8f..903e30996ec 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -92,7 +92,7 @@ async function detectNextDependency(path: string): Promise { return packageJsonContent.dependencies?.next !== undefined; } -async function detectUseOfSrcDir(path: string): Promise { +export async function detectUseOfSrcDir(path: string): Promise { // Detects if the project is using a src directory try { await fs.access(pathModule.join(path, "src")); diff --git a/packages/cli/src/frameworks/nextjs/install.test.ts b/packages/cli/src/frameworks/nextjs/install.test.ts new file mode 100644 index 00000000000..8e3dd15f973 --- /dev/null +++ b/packages/cli/src/frameworks/nextjs/install.test.ts @@ -0,0 +1,19 @@ +import mock from "mock-fs"; +// import { detectUseOfSrcDir } from "."; + +afterEach(() => { + mock.restore(); +}); + +describe("install", () => { + test("detect use of src directory", async () => { + mock({ + src: { + "some-file.txt": "file content here", + }, + }); + + // const hasSrcDirectory = await detectUseOfSrcDir("/"); + expect(true).toEqual(true); + }); +}); From adf0ef1f5681abaef8a6fd8f52defa69c7799d2f Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 13:03:15 +0100 Subject: [PATCH 06/53] Delete old jest config --- packages/cli/jest.config.js | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 packages/cli/jest.config.js diff --git a/packages/cli/jest.config.js b/packages/cli/jest.config.js deleted file mode 100644 index f17734821da..00000000000 --- a/packages/cli/jest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -export default { - preset: 'ts-jest', - testEnvironment: 'node', - testMatch: [ - "/test/**/*.ts?(x)", - "/test/**/?(*.)+(spec|test).ts?(x)", - "/src/**/?(*.)+(spec|test).ts?(x)" - ], -}; \ No newline at end of file From 01eca1e2af648a22648086ebaaf5efafa6c14f31 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 13:03:23 +0100 Subject: [PATCH 07/53] Latest lockfile --- pnpm-lock.yaml | 632 ++++++++----------------------------------------- 1 file changed, 98 insertions(+), 534 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 945273c245b..152e846efc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -465,37 +465,6 @@ importers: '@trigger.dev/cli': link:../../packages/cli concurrently: 8.2.0 - examples/my-app: - specifiers: - '@trigger.dev/cli': workspace:* - '@types/node': 20.6.0 - '@types/react': 18.2.21 - '@types/react-dom': 18.2.7 - autoprefixer: 10.4.15 - eslint: 8.49.0 - eslint-config-next: 13.4.19 - next: 13.4.19 - postcss: 8.4.29 - react: 18.2.0 - react-dom: 18.2.0 - tailwindcss: 3.3.3 - typescript: 5.2.2 - dependencies: - '@types/node': 20.6.0 - '@types/react': 18.2.21 - '@types/react-dom': 18.2.7 - autoprefixer: 10.4.15_postcss@8.4.29 - eslint: 8.49.0 - eslint-config-next: 13.4.19_rngtr6f3b25lvetpihwplgecf4 - next: 13.4.19_biqbaboplfbrettd7655fr4n2y - postcss: 8.4.29 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - tailwindcss: 3.3.3 - typescript: 5.2.2 - devDependencies: - '@trigger.dev/cli': link:../../packages/cli - examples/nextjs-12: specifiers: '@trigger.dev/cli': workspace:* @@ -968,7 +937,7 @@ importers: '@types/degit': ^2.8.3 '@types/gradient-string': ^1.1.2 '@types/inquirer': ^9.0.3 - '@types/jest': ^29.5.3 + '@types/mock-fs': ^4.13.1 '@types/node': '16' '@types/node-fetch': ^2.6.2 chalk: ^5.2.0 @@ -979,8 +948,8 @@ importers: execa: ^7.0.0 gradient-string: ^2.0.2 inquirer: ^9.1.4 - jest: ^29.6.2 localtunnel: ^2.0.2 + mock-fs: ^5.2.0 nanoid: ^4.0.2 ngrok: 5.0.0-beta.2 node-fetch: ^3.3.0 @@ -993,12 +962,12 @@ importers: rimraf: ^3.0.2 simple-git: ^3.19.0 terminal-link: ^3.0.0 - ts-jest: ^29.1.1 tsconfck: ^2.1.2 tsup: ^6.5.0 type-fest: ^3.6.0 typescript: ^4.9.5 url: ^0.11.1 + vitest: ^0.34.4 zod: 3.21.4 dependencies: '@types/degit': 2.8.3 @@ -1030,15 +999,15 @@ importers: '@trigger.dev/tsconfig': link:../../config-packages/tsconfig '@types/gradient-string': 1.1.2 '@types/inquirer': 9.0.3 - '@types/jest': 29.5.3 + '@types/mock-fs': 4.13.1 '@types/node': 16.18.11 '@types/node-fetch': 2.6.2 - jest: 29.6.2_@types+node@16.18.11 + mock-fs: 5.2.0 rimraf: 3.0.2 - ts-jest: 29.1.1_xlkreayjyan5lfqjb5gzdqf3my tsup: 6.6.3_typescript@4.9.5 type-fest: 3.6.0 typescript: 4.9.5 + vitest: 0.34.4 packages/core: specifiers: @@ -4764,6 +4733,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm/0.16.4: @@ -4815,6 +4785,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm64/0.16.4: @@ -4866,6 +4837,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-x64/0.16.4: @@ -4917,6 +4889,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-arm64/0.16.4: @@ -4968,6 +4941,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-x64/0.16.4: @@ -5019,6 +4993,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-arm64/0.16.4: @@ -5070,6 +5045,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-x64/0.16.4: @@ -5121,6 +5097,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm/0.16.4: @@ -5172,6 +5149,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm64/0.16.4: @@ -5223,6 +5201,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ia32/0.16.4: @@ -5283,6 +5262,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-loong64/0.16.4: @@ -5334,6 +5314,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-mips64el/0.16.4: @@ -5385,6 +5366,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ppc64/0.16.4: @@ -5436,6 +5418,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-riscv64/0.16.4: @@ -5487,6 +5470,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-s390x/0.16.4: @@ -5538,6 +5522,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-x64/0.16.4: @@ -5589,6 +5574,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: true optional: true /@esbuild/netbsd-x64/0.16.4: @@ -5640,6 +5626,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: true optional: true /@esbuild/openbsd-x64/0.16.4: @@ -5691,6 +5678,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: true optional: true /@esbuild/sunos-x64/0.16.4: @@ -5742,6 +5730,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-arm64/0.16.4: @@ -5793,6 +5782,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-ia32/0.16.4: @@ -5844,6 +5834,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-x64/0.16.4: @@ -5928,25 +5919,10 @@ packages: eslint: 8.45.0 eslint-visitor-keys: 3.4.2 - /@eslint-community/eslint-utils/4.4.0_eslint@8.49.0: - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.49.0 - eslint-visitor-keys: 3.4.2 - dev: false - /@eslint-community/regexpp/4.5.1: resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - /@eslint-community/regexpp/4.8.1: - resolution: {integrity: sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: false - /@eslint/eslintrc/1.4.1: resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5980,23 +5956,6 @@ packages: transitivePeerDependencies: - supports-color - /@eslint/eslintrc/2.1.2: - resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.6.1 - globals: 13.19.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: false - /@eslint/js/8.42.0: resolution: {integrity: sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6006,11 +5965,6 @@ packages: resolution: {integrity: sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /@eslint/js/8.49.0: - resolution: {integrity: sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: false - /@fal-works/esbuild-plugin-global-externals/2.1.2: resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} dev: true @@ -6247,17 +6201,6 @@ packages: transitivePeerDependencies: - supports-color - /@humanwhocodes/config-array/0.11.11: - resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: false - /@humanwhocodes/config-array/0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} @@ -6330,14 +6273,14 @@ packages: '@jest/test-result': 29.6.2 '@jest/transform': 29.6.2 '@jest/types': 29.6.1 - '@types/node': 20.5.2 + '@types/node': 20.6.0 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.8.0 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 29.5.0 - jest-config: 29.6.2_@types+node@20.5.2 + jest-config: 29.6.2_@types+node@20.6.0 jest-haste-map: 29.6.2 jest-message-util: 29.6.2 jest-regex-util: 29.4.3 @@ -6546,7 +6489,7 @@ packages: '@jest/schemas': 29.6.0 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.5.2 + '@types/node': 20.6.0 '@types/yargs': 17.0.19 chalk: 4.1.2 dev: true @@ -6774,10 +6717,6 @@ packages: resolution: {integrity: sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==} dev: false - /@next/env/13.4.19: - resolution: {integrity: sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==} - dev: false - /@next/eslint-plugin-next/12.3.4: resolution: {integrity: sha512-BFwj8ykJY+zc1/jWANsDprDIu2MgwPOIKxNVnrKvPs+f5TPegrVnem8uScND+1veT4B7F6VeqgaNLFW1Hzl9Og==} dependencies: @@ -6796,12 +6735,6 @@ packages: glob: 7.1.7 dev: false - /@next/eslint-plugin-next/13.4.19: - resolution: {integrity: sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==} - dependencies: - glob: 7.1.7 - dev: false - /@next/eslint-plugin-next/13.4.6: resolution: {integrity: sha512-bPigeu0RI7bgy1ucBA2Yqcfg539y0Lzo38P2hIkrRB1GNvFSbYg6RTu8n6tGqPVrH3TTlPTNKLXG01wc+5NuwQ==} dependencies: @@ -6861,15 +6794,6 @@ packages: dev: false optional: true - /@next/swc-darwin-arm64/13.4.19: - resolution: {integrity: sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - /@next/swc-darwin-x64/12.3.4: resolution: {integrity: sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==} engines: {node: '>= 10'} @@ -6905,15 +6829,6 @@ packages: dev: false optional: true - /@next/swc-darwin-x64/13.4.19: - resolution: {integrity: sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - /@next/swc-freebsd-x64/12.3.4: resolution: {integrity: sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==} engines: {node: '>= 10'} @@ -6967,15 +6882,6 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu/13.4.19: - resolution: {integrity: sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-arm64-musl/12.3.4: resolution: {integrity: sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==} engines: {node: '>= 10'} @@ -7011,15 +6917,6 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl/13.4.19: - resolution: {integrity: sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-x64-gnu/12.3.4: resolution: {integrity: sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==} engines: {node: '>= 10'} @@ -7055,15 +6952,6 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu/13.4.19: - resolution: {integrity: sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-x64-musl/12.3.4: resolution: {integrity: sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==} engines: {node: '>= 10'} @@ -7099,15 +6987,6 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl/13.4.19: - resolution: {integrity: sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-arm64-msvc/12.3.4: resolution: {integrity: sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==} engines: {node: '>= 10'} @@ -7143,15 +7022,6 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc/13.4.19: - resolution: {integrity: sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-ia32-msvc/12.3.4: resolution: {integrity: sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==} engines: {node: '>= 10'} @@ -7187,15 +7057,6 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc/13.4.19: - resolution: {integrity: sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-x64-msvc/12.3.4: resolution: {integrity: sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==} engines: {node: '>= 10'} @@ -7231,15 +7092,6 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc/13.4.19: - resolution: {integrity: sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@nicolo-ribaudo/eslint-scope-5-internals/5.1.1-v1: resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} dependencies: @@ -10269,7 +10121,7 @@ packages: resolution: {integrity: sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==} engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} dependencies: - '@types/node': 20.5.2 + '@types/node': 20.6.0 dev: false /@slack/types/2.8.0: @@ -12367,7 +12219,7 @@ packages: /@types/is-stream/1.1.0: resolution: {integrity: sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==} dependencies: - '@types/node': 20.5.2 + '@types/node': 20.6.0 dev: false /@types/istanbul-lib-coverage/2.0.4: @@ -12514,6 +12366,12 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: false + /@types/mock-fs/4.13.1: + resolution: {integrity: sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==} + dependencies: + '@types/node': 20.6.0 + dev: true + /@types/morgan/1.9.4: resolution: {integrity: sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==} dependencies: @@ -12544,7 +12402,7 @@ packages: /@types/node-fetch/2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 20.5.2 + '@types/node': 20.6.0 form-data: 3.0.1 /@types/node/12.20.55: @@ -12583,6 +12441,7 @@ packages: /@types/node/20.5.2: resolution: {integrity: sha512-5j/lXt7unfPOUlrKC34HIaedONleyLtwkKggiD/0uuMfT8gg2EOpg0dz4lCD15Ga7muC+1WzJZAjIB9simWd6Q==} + dev: true /@types/node/20.6.0: resolution: {integrity: sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==} @@ -12617,7 +12476,7 @@ packages: /@types/pg/8.6.6: resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} dependencies: - '@types/node': 20.5.2 + '@types/node': 20.6.0 pg-protocol: 1.6.0 pg-types: 2.2.0 dev: false @@ -12732,7 +12591,7 @@ packages: /@types/through/0.0.30: resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==} dependencies: - '@types/node': 20.5.2 + '@types/node': 20.6.0 dev: true /@types/tinycolor2/1.4.3: @@ -12958,26 +12817,6 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.59.6_rngtr6f3b25lvetpihwplgecf4: - resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 5.59.6 - '@typescript-eslint/types': 5.59.6 - '@typescript-eslint/typescript-estree': 5.59.6_typescript@5.2.2 - debug: 4.3.4 - eslint: 8.49.0 - typescript: 5.2.2 - transitivePeerDependencies: - - supports-color - dev: false - /@typescript-eslint/scope-manager/5.59.6: resolution: {integrity: sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -13132,27 +12971,6 @@ packages: transitivePeerDependencies: - supports-color - /@typescript-eslint/typescript-estree/5.59.6_typescript@5.2.2: - resolution: {integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 5.59.6 - '@typescript-eslint/visitor-keys': 5.59.6 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.4 - tsutils: 3.21.0_typescript@5.2.2 - typescript: 5.2.2 - transitivePeerDependencies: - - supports-color - dev: false - /@typescript-eslint/utils/5.59.6_eslint@8.45.0: resolution: {integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -14196,22 +14014,6 @@ packages: postcss-value-parser: 4.2.0 dev: false - /autoprefixer/10.4.15_postcss@8.4.29: - resolution: {integrity: sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.21.10 - caniuse-lite: 1.0.30001532 - fraction.js: 4.2.0 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.29 - postcss-value-parser: 4.2.0 - dev: false - /available-typed-arrays/1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} @@ -16864,6 +16666,7 @@ packages: '@esbuild/win32-arm64': 0.16.17 '@esbuild/win32-ia32': 0.16.17 '@esbuild/win32-x64': 0.16.17 + dev: true /esbuild/0.16.4: resolution: {integrity: sha512-qQrPMQpPTWf8jHugLWHoGqZjApyx3OEm76dlTXobHwh/EBbavbRdjXdYi/GWr43GyN0sfpap14GPkb05NH3ROA==} @@ -17140,31 +16943,6 @@ packages: - supports-color dev: false - /eslint-config-next/13.4.19_rngtr6f3b25lvetpihwplgecf4: - resolution: {integrity: sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==} - peerDependencies: - eslint: ^7.23.0 || ^8.0.0 - typescript: '>=3.3.1' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@next/eslint-plugin-next': 13.4.19 - '@rushstack/eslint-patch': 1.2.0 - '@typescript-eslint/parser': 5.59.6_rngtr6f3b25lvetpihwplgecf4 - eslint: 8.49.0 - eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.5_3gbtcjnbjinsubg5cwv3525lbq - eslint-plugin-import: 2.27.5_tyljvsdz2ipp3eruvtbrjckloe - eslint-plugin-jsx-a11y: 6.7.1_eslint@8.49.0 - eslint-plugin-react: 7.32.2_eslint@8.49.0 - eslint-plugin-react-hooks: 4.6.0_eslint@8.49.0 - typescript: 5.2.2 - transitivePeerDependencies: - - eslint-import-resolver-webpack - - supports-color - dev: false - /eslint-config-next/13.4.6_binxsscxvozjxebftqdoazsxm4: resolution: {integrity: sha512-nlv4FYish1RYYHILbQwM5/rD37cOvEqtMfDjtQCYbXdE2O3MggqHu2q6IDeLE2Z6u8ZJyNPgWOA6OimWcxj3qw==} peerDependencies: @@ -17284,30 +17062,6 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript/3.5.5_3gbtcjnbjinsubg5cwv3525lbq: - resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - dependencies: - debug: 4.3.4 - enhanced-resolve: 5.13.0 - eslint: 8.49.0 - eslint-module-utils: 2.7.4_bytowunmwsvn7w25lnp5m6zpem - eslint-plugin-import: 2.27.5_tyljvsdz2ipp3eruvtbrjckloe - get-tsconfig: 4.5.0 - globby: 13.1.3 - is-core-module: 2.11.0 - is-glob: 4.0.3 - synckit: 0.8.5 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - dev: false - /eslint-import-resolver-typescript/3.5.5_bsgoee2ktd7nzirwexa3gasa7m: resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -17409,36 +17163,6 @@ packages: - supports-color dev: true - /eslint-module-utils/2.7.4_bytowunmwsvn7w25lnp5m6zpem: - resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - '@typescript-eslint/parser': 5.59.6_rngtr6f3b25lvetpihwplgecf4 - debug: 3.2.7 - eslint: 8.49.0 - eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.5_3gbtcjnbjinsubg5cwv3525lbq - transitivePeerDependencies: - - supports-color - dev: false - /eslint-module-utils/2.7.4_hhwxwo5vg2mzr5fq3eei7kqaye: resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} engines: {node: '>=4'} @@ -17660,39 +17384,6 @@ packages: - supports-color dev: true - /eslint-plugin-import/2.27.5_tyljvsdz2ipp3eruvtbrjckloe: - resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - dependencies: - '@typescript-eslint/parser': 5.59.6_rngtr6f3b25lvetpihwplgecf4 - array-includes: 3.1.6 - array.prototype.flat: 1.3.1 - array.prototype.flatmap: 1.3.1 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.49.0 - eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.7.4_bytowunmwsvn7w25lnp5m6zpem - has: 1.0.3 - is-core-module: 2.11.0 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.values: 1.1.6 - resolve: 1.22.2 - semver: 6.3.0 - tsconfig-paths: 3.14.1 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: false - /eslint-plugin-import/2.27.5_xpt3ce3kmhzhmuk3dcuwe6u2pe: resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} @@ -17890,31 +17581,6 @@ packages: object.fromentries: 2.0.6 semver: 6.3.0 - /eslint-plugin-jsx-a11y/6.7.1_eslint@8.49.0: - resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - dependencies: - '@babel/runtime': 7.22.5 - aria-query: 5.1.3 - array-includes: 3.1.6 - array.prototype.flatmap: 1.3.1 - ast-types-flow: 0.0.7 - axe-core: 4.6.2 - axobject-query: 3.1.1 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 8.49.0 - has: 1.0.3 - jsx-ast-utils: 3.3.3 - language-tags: 1.0.5 - minimatch: 3.1.2 - object.entries: 1.1.6 - object.fromentries: 2.0.6 - semver: 6.3.0 - dev: false - /eslint-plugin-node/11.1.0_eslint@8.31.0: resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} engines: {node: '>=8.10.0'} @@ -17981,15 +17647,6 @@ packages: eslint: 8.45.0 dev: true - /eslint-plugin-react-hooks/4.6.0_eslint@8.49.0: - resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - dependencies: - eslint: 8.49.0 - dev: false - /eslint-plugin-react-hooks/5.0.0-canary-7118f5dd7-20230705_eslint@8.45.0: resolution: {integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==} engines: {node: '>=10'} @@ -18117,30 +17774,6 @@ packages: semver: 6.3.0 string.prototype.matchall: 4.0.8 - /eslint-plugin-react/7.32.2_eslint@8.49.0: - resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - dependencies: - array-includes: 3.1.6 - array.prototype.flatmap: 1.3.1 - array.prototype.tosorted: 1.1.1 - doctrine: 2.1.0 - eslint: 8.49.0 - estraverse: 5.3.0 - jsx-ast-utils: 3.3.3 - minimatch: 3.1.2 - object.entries: 1.1.6 - object.fromentries: 2.0.6 - object.hasown: 1.1.2 - object.values: 1.1.6 - prop-types: 15.8.1 - resolve: 2.0.0-next.4 - semver: 6.3.0 - string.prototype.matchall: 4.0.8 - dev: false - /eslint-plugin-testing-library/5.11.0_iukboom6ndih5an6iafl45j2fe: resolution: {integrity: sha512-ELY7Gefo+61OfXKlQeXNIDVVLPcvKTeiQOoMZG9TeuWa7Ln4dUNRv8JdRWBQI9Mbb427XGlVB1aa1QPZxBJM8Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} @@ -18199,14 +17832,6 @@ packages: esrecurse: 4.3.0 estraverse: 5.3.0 - /eslint-scope/7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: false - /eslint-utils/2.1.0: resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} engines: {node: '>=6'} @@ -18451,52 +18076,6 @@ packages: transitivePeerDependencies: - supports-color - /eslint/8.49.0: - resolution: {integrity: sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0_eslint@8.49.0 - '@eslint-community/regexpp': 4.8.1 - '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.49.0 - '@humanwhocodes/config-array': 0.11.11 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.19.0 - graphemer: 1.4.0 - ignore: 5.2.4 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.3 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: false - /espree/9.4.1: resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -18514,15 +18093,6 @@ packages: acorn-jsx: 5.3.2_acorn@8.10.0 eslint-visitor-keys: 3.4.2 - /espree/9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.10.0 - acorn-jsx: 5.3.2_acorn@8.10.0 - eslint-visitor-keys: 3.4.3 - dev: false - /esprima/4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -18734,7 +18304,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/expect-utils': 29.6.2 - '@types/node': 20.5.2 + '@types/node': 20.6.0 jest-get-type: 29.4.3 jest-matcher-utils: 29.6.2 jest-message-util: 29.6.2 @@ -21217,7 +20787,7 @@ packages: - supports-color dev: true - /jest-config/29.6.2_@types+node@20.5.2: + /jest-config/29.6.2_@types+node@20.6.0: resolution: {integrity: sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -21232,7 +20802,7 @@ packages: '@babel/core': 7.22.17 '@jest/test-sequencer': 29.6.2 '@jest/types': 29.6.1 - '@types/node': 20.5.2 + '@types/node': 20.6.0 babel-jest: 29.6.2_@babel+core@7.22.17 chalk: 4.1.2 ci-info: 3.8.0 @@ -21378,7 +20948,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 20.5.2 + '@types/node': 20.6.0 dev: true /jest-mock/29.6.2: @@ -21524,7 +21094,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.1 - '@types/node': 20.5.2 + '@types/node': 20.6.0 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.10 @@ -23207,6 +22777,11 @@ packages: yargs-unparser: 2.0.0 dev: true + /mock-fs/5.2.0: + resolution: {integrity: sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==} + engines: {node: '>=12.0.0'} + dev: true + /module-details-from-path/1.0.3: resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} dev: false @@ -23604,46 +23179,6 @@ packages: - babel-plugin-macros dev: false - /next/13.4.19_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==} - engines: {node: '>=16.8.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - sass: - optional: true - dependencies: - '@next/env': 13.4.19 - '@swc/helpers': 0.5.1 - busboy: 1.6.0 - caniuse-lite: 1.0.30001532 - postcss: 8.4.14 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - styled-jsx: 5.1.1_react@18.2.0 - watchpack: 2.4.0 - zod: 3.21.4 - optionalDependencies: - '@next/swc-darwin-arm64': 13.4.19 - '@next/swc-darwin-x64': 13.4.19 - '@next/swc-linux-arm64-gnu': 13.4.19 - '@next/swc-linux-arm64-musl': 13.4.19 - '@next/swc-linux-x64-gnu': 13.4.19 - '@next/swc-linux-x64-musl': 13.4.19 - '@next/swc-win32-arm64-msvc': 13.4.19 - '@next/swc-win32-ia32-msvc': 13.4.19 - '@next/swc-win32-x64-msvc': 13.4.19 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: false - /ngrok/5.0.0-beta.2: resolution: {integrity: sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ==} engines: {node: '>=14.2'} @@ -26733,6 +26268,7 @@ packages: hasBin: true optionalDependencies: fsevents: 2.3.2 + dev: true /rollup/3.29.1: resolution: {integrity: sha512-c+ebvQz0VIH4KhhCpDsI+Bik0eT8ZFEVZEYw0cGMVqIP8zc+gnwl7iXCamTw7vzv2MeuZFZfdx5JJIq+ehzDlg==} @@ -28840,16 +28376,6 @@ packages: tslib: 1.14.1 typescript: 5.1.6 - /tsutils/3.21.0_typescript@5.2.2: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - typescript: 5.2.2 - dev: false - /tsx/3.12.2: resolution: {integrity: sha512-ykAEkoBg30RXxeOMVeZwar+JH632dZn9EUJVyJwhfag62k6UO/dIyJEV58YuLF6e5BTdV/qmbQrpkWqjq9cUnQ==} hasBin: true @@ -29071,6 +28597,7 @@ packages: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} hasBin: true + dev: true /ufo/1.3.0: resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} @@ -29709,7 +29236,7 @@ packages: - terser dev: true - /vite-node/0.34.4_@types+node@20.5.2: + /vite-node/0.34.4_@types+node@20.6.0: resolution: {integrity: sha512-ho8HtiLc+nsmbwZMw8SlghESEE3KxJNp04F/jPUCLVvaURwt0d+r9LxEqCX5hvrrOQ0GSyxbYr5ZfRYhQ0yVKQ==} engines: {node: '>=v14.18.0'} hasBin: true @@ -29719,7 +29246,7 @@ packages: mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.4.9_@types+node@20.5.2 + vite: 4.4.9_@types+node@20.6.0 transitivePeerDependencies: - '@types/node' - less @@ -29874,6 +29401,7 @@ packages: rollup: 3.10.0 optionalDependencies: fsevents: 2.3.2 + dev: true /vite/4.4.9: resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} @@ -29979,6 +29507,42 @@ packages: rollup: 3.29.1 optionalDependencies: fsevents: 2.3.2 + dev: true + + /vite/4.4.9_@types+node@20.6.0: + resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.6.0 + esbuild: 0.18.11 + postcss: 8.4.29 + rollup: 3.29.1 + optionalDependencies: + fsevents: 2.3.2 /vitefu/0.2.4_vite@4.4.9: resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} @@ -30079,7 +29643,7 @@ packages: dependencies: '@types/chai': 4.3.6 '@types/chai-subset': 1.3.3 - '@types/node': 20.5.2 + '@types/node': 20.6.0 '@vitest/expect': 0.34.4 '@vitest/runner': 0.34.4 '@vitest/snapshot': 0.34.4 @@ -30098,8 +29662,8 @@ packages: strip-literal: 1.0.1 tinybench: 2.5.0 tinypool: 0.7.0 - vite: 4.1.4_@types+node@20.5.2 - vite-node: 0.34.4_@types+node@20.5.2 + vite: 4.4.9_@types+node@20.6.0 + vite-node: 0.34.4_@types+node@20.6.0 why-is-node-running: 2.2.2 transitivePeerDependencies: - less From a763d77f186eb41adf2b6f3fbdd005acb9534688 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 13:05:17 +0100 Subject: [PATCH 08/53] Detect use of src directory test --- packages/cli/src/frameworks/nextjs/install.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/install.test.ts b/packages/cli/src/frameworks/nextjs/install.test.ts index 8e3dd15f973..d67848e5107 100644 --- a/packages/cli/src/frameworks/nextjs/install.test.ts +++ b/packages/cli/src/frameworks/nextjs/install.test.ts @@ -1,4 +1,5 @@ import mock from "mock-fs"; +import { detectUseOfSrcDir } from "."; // import { detectUseOfSrcDir } from "."; afterEach(() => { @@ -13,7 +14,7 @@ describe("install", () => { }, }); - // const hasSrcDirectory = await detectUseOfSrcDir("/"); - expect(true).toEqual(true); + const hasSrcDirectory = await detectUseOfSrcDir(""); + expect(hasSrcDirectory).toEqual(true); }); }); From c5be50dbc6d2d1366f90bf9c555e11d179b529d5 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 13:20:21 +0100 Subject: [PATCH 09/53] Tests for detection pages/app directory --- packages/cli/src/frameworks/nextjs/index.ts | 30 +++------- .../cli/src/frameworks/nextjs/install.test.ts | 59 ++++++++++++++++++- 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 903e30996ec..9aec2ebe768 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -45,7 +45,7 @@ export class NextJs implements Framework { logger.info("πŸ“ Detected use of src directory"); } - const nextJsDir = await detectPagesOrAppDir(path, usesSrcDir); + const nextJsDir = await detectPagesOrAppDir(path); const routeDir = pathModule.join(path, usesSrcDir ? "src" : ""); @@ -102,31 +102,15 @@ export async function detectUseOfSrcDir(path: string): Promise { } } -// Detect the use of pages or app dir in the Next.js project -// Import the next.config.js file and check for experimental: { appDir: true } -async function detectPagesOrAppDir(path: string, usesSrcDir = false): Promise<"pages" | "app"> { - const nextConfigPath = pathModule.join(path, "next.config.js"); - const importedConfig = await import(pathToFileURL(nextConfigPath).toString()).catch(() => ({})); - - if (importedConfig?.default?.experimental?.appDir) { +export async function detectPagesOrAppDir(path: string): Promise<"pages" | "app"> { + const withoutSrcAppPath = pathModule.join(path, "app"); + if (await pathExists(withoutSrcAppPath)) { return "app"; } - // We need to check if src/app/page.tsx exists - // Or app/page.tsx exists - // If so then we return app - // If not return pages - - const extensionsToCheck = ["jsx", "tsx", "js", "ts"]; - const basePath = pathModule.join(path, usesSrcDir ? "src" : "", "app", `page.`); - - for (const extension of extensionsToCheck) { - const appPagePath = basePath + extension; - const appPageExists = await pathExists(appPagePath); - - if (appPageExists) { - return "app"; - } + const withSrcAppPath = pathModule.join(path, "src", "app"); + if (await pathExists(withSrcAppPath)) { + return "app"; } return "pages"; diff --git a/packages/cli/src/frameworks/nextjs/install.test.ts b/packages/cli/src/frameworks/nextjs/install.test.ts index d67848e5107..d75afb92d58 100644 --- a/packages/cli/src/frameworks/nextjs/install.test.ts +++ b/packages/cli/src/frameworks/nextjs/install.test.ts @@ -1,13 +1,13 @@ import mock from "mock-fs"; -import { detectUseOfSrcDir } from "."; +import { detectPagesOrAppDir, detectUseOfSrcDir } from "."; // import { detectUseOfSrcDir } from "."; afterEach(() => { mock.restore(); }); -describe("install", () => { - test("detect use of src directory", async () => { +describe("src directory", () => { + test("has src directory", async () => { mock({ src: { "some-file.txt": "file content here", @@ -17,4 +17,57 @@ describe("install", () => { const hasSrcDirectory = await detectUseOfSrcDir(""); expect(hasSrcDirectory).toEqual(true); }); + + test("no src directory", async () => { + mock({ + app: { + "some-file.txt": "file content here", + }, + }); + + const hasSrcDirectory = await detectUseOfSrcDir(""); + expect(hasSrcDirectory).toEqual(false); + }); +}); + +describe("detect pages or app directory", () => { + test("detect 'app' from src/app directory", async () => { + mock({ + "src/app": {}, + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("app"); + }); + + test("detect 'app' from src/app directory", async () => { + mock({ + "src/app": {}, + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("app"); + }); + + test("detect 'pages' from src/pages directory", async () => { + mock({ + "src/pages": { + "some-file.txt": "file content here", + }, + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("pages"); + }); + + test("detect 'pages' from pages directory", async () => { + mock({ + pages: { + "some-file.txt": "file content here", + }, + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("pages"); + }); }); From cb1e4239686f41babf29a5f32d084fe345e83e9d Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 13:25:54 +0100 Subject: [PATCH 10/53] Correct detection of Next.js project --- packages/cli/src/frameworks/nextjs/index.ts | 8 ++----- .../cli/src/frameworks/nextjs/install.test.ts | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 9aec2ebe768..e9463fddce5 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -1,4 +1,3 @@ -import { pathToFileURL } from "url"; import { Framework } from ".."; import { InstallPackage } from "../../utils/addDependencies"; import { PackageManager } from "../../utils/getUserPkgManager"; @@ -77,13 +76,10 @@ export class NextJs implements Framework { } async function detectNextConfigFile(path: string): Promise { - return fs - .access(pathModule.join(path, "next.config.js")) - .then(() => true) - .catch(() => false); + return pathExists(pathModule.join(path, "next.config.js")); } -async function detectNextDependency(path: string): Promise { +export async function detectNextDependency(path: string): Promise { const packageJsonContent = await readPackageJson(path); if (!packageJsonContent) { return false; diff --git a/packages/cli/src/frameworks/nextjs/install.test.ts b/packages/cli/src/frameworks/nextjs/install.test.ts index d75afb92d58..4895ad8b4fc 100644 --- a/packages/cli/src/frameworks/nextjs/install.test.ts +++ b/packages/cli/src/frameworks/nextjs/install.test.ts @@ -1,11 +1,32 @@ import mock from "mock-fs"; import { detectPagesOrAppDir, detectUseOfSrcDir } from "."; +import { getFramework } from ".."; // import { detectUseOfSrcDir } from "."; afterEach(() => { mock.restore(); }); +describe("Next project detection", () => { + test("has dependency", async () => { + mock({ + "package.json": JSON.stringify({ dependencies: { next: "1.0.0" } }), + }); + + const framework = await getFramework("", "npm"); + expect(framework?.id).toEqual("nextjs"); + }); + + test("no dependency", async () => { + mock({ + "package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }), + }); + + const framework = await getFramework("", "npm"); + expect(framework?.id).not.toEqual("nextjs"); + }); +}); + describe("src directory", () => { test("has src directory", async () => { mock({ From f9103b9f68d6a191f7c5a9d5ddc52f248bf6a0c1 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 13:58:15 +0100 Subject: [PATCH 11/53] Renamed test file --- .../src/frameworks/nextjs/{install.test.ts => nextjs.test.ts} | 1 - 1 file changed, 1 deletion(-) rename packages/cli/src/frameworks/nextjs/{install.test.ts => nextjs.test.ts} (98%) diff --git a/packages/cli/src/frameworks/nextjs/install.test.ts b/packages/cli/src/frameworks/nextjs/nextjs.test.ts similarity index 98% rename from packages/cli/src/frameworks/nextjs/install.test.ts rename to packages/cli/src/frameworks/nextjs/nextjs.test.ts index 4895ad8b4fc..a691d36b226 100644 --- a/packages/cli/src/frameworks/nextjs/install.test.ts +++ b/packages/cli/src/frameworks/nextjs/nextjs.test.ts @@ -1,7 +1,6 @@ import mock from "mock-fs"; import { detectPagesOrAppDir, detectUseOfSrcDir } from "."; import { getFramework } from ".."; -// import { detectUseOfSrcDir } from "."; afterEach(() => { mock.restore(); From 1b7134c78bf7d9f410984b71bda7c09e11081b90 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 13:58:31 +0100 Subject: [PATCH 12/53] Create install files from template files with replacements. With tests --- .../src/frameworks/nextjs/files/apiRoute.js | 10 ++++ .../src/utils/createFileFromTemplate.test.ts | 56 +++++++++++++++++ .../cli/src/utils/createFileFromTemplate.ts | 60 +++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 packages/cli/src/frameworks/nextjs/files/apiRoute.js create mode 100644 packages/cli/src/utils/createFileFromTemplate.test.ts create mode 100644 packages/cli/src/utils/createFileFromTemplate.ts diff --git a/packages/cli/src/frameworks/nextjs/files/apiRoute.js b/packages/cli/src/frameworks/nextjs/files/apiRoute.js new file mode 100644 index 00000000000..213055b828e --- /dev/null +++ b/packages/cli/src/frameworks/nextjs/files/apiRoute.js @@ -0,0 +1,10 @@ +import { createPagesRoute } from "@trigger.dev/nextjs"; +import { client } from "${routePathPrefix}trigger"; + +import "${routePathPrefix}jobs"; + +//this route is used to send and receive data with Trigger.dev +const { handler, config } = createPagesRoute(client); +export { config }; + +export default handler; diff --git a/packages/cli/src/utils/createFileFromTemplate.test.ts b/packages/cli/src/utils/createFileFromTemplate.test.ts new file mode 100644 index 00000000000..c6ef57b0b26 --- /dev/null +++ b/packages/cli/src/utils/createFileFromTemplate.test.ts @@ -0,0 +1,56 @@ +import mock from "mock-fs"; +import { createFileFromTemplate, replaceAll } from "./createFileFromTemplate"; +import fs from "fs/promises"; +import exp from "constants"; + +afterEach(() => { + mock.restore(); +}); + +const preReplacement = `import { createPagesRoute } from "@trigger.dev/nextjs"; +import { client } from "\${routePathPrefix}trigger"; +import "\${anotherPathPrefix}jobs"; + +const { handler, config } = createPagesRoute(client);`; + +const postReplacement = `import { createPagesRoute } from "@trigger.dev/nextjs"; +import { client } from "@/trigger"; +import "@/src/jobs"; + +const { handler, config } = createPagesRoute(client);`; + +describe("Replace function", () => { + test("simple replacements", async () => { + const output = replaceAll(preReplacement, { + routePathPrefix: "@/", + anotherPathPrefix: "@/src/", + }); + expect(output).toEqual(postReplacement); + }); +}); + +describe("Template files", () => { + test("basic template", async () => { + mock({ + templates: { + "some-file.js": preReplacement, + }, + }); + + const result = await createFileFromTemplate({ + templatePath: "templates/some-file.js", + replacements: { + routePathPrefix: "@/", + anotherPathPrefix: "@/src/", + }, + outputPath: "foo/output.ts", + }); + + expect(result.success).toEqual(true); + if (!result.success) return; + expect(result.alreadyExisted).toEqual(false); + + const fileContents = await fs.readFile("foo/output.ts", "utf-8"); + expect(fileContents).toEqual(postReplacement); + }); +}); diff --git a/packages/cli/src/utils/createFileFromTemplate.ts b/packages/cli/src/utils/createFileFromTemplate.ts new file mode 100644 index 00000000000..29e8e25074a --- /dev/null +++ b/packages/cli/src/utils/createFileFromTemplate.ts @@ -0,0 +1,60 @@ +import fs from "fs/promises"; +import { pathExists } from "./fileSystem"; +import path from "path"; + +type Result = + | { + success: true; + alreadyExisted: boolean; + } + | { + success: false; + error: string; + }; + +export async function createFileFromTemplate(params: { + templatePath: string; + replacements: Record; + outputPath: string; +}): Promise { + if (await pathExists(params.outputPath)) { + return { + success: true, + alreadyExisted: true, + }; + } + + try { + const template = await fs.readFile(params.templatePath, "utf-8"); + const output = replaceAll(template, params.replacements); + + const directoryName = path.dirname(params.outputPath); + await fs.mkdir(directoryName, { recursive: true }); + await fs.writeFile(params.outputPath, output); + + return { + success: true, + alreadyExisted: false, + }; + } catch (e) { + if (e instanceof Error) { + return { + success: false, + error: e.message, + }; + } + return { + success: false, + error: JSON.stringify(e), + }; + } +} + +// find strings that match ${varName} and replace with the value from a Record where { varName: "value" } +export function replaceAll(input: string, replacements: Record) { + let output = input; + for (const [key, value] of Object.entries(replacements)) { + output = output.replace(new RegExp(`\\$\\{${key}\\}`, "g"), value); + } + return output; +} From 5a11cba49e747c5c4fe64714141651e219193683 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 14:17:07 +0100 Subject: [PATCH 13/53] Added multiple uses of the same replacement --- packages/cli/src/utils/createFileFromTemplate.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cli/src/utils/createFileFromTemplate.test.ts b/packages/cli/src/utils/createFileFromTemplate.test.ts index c6ef57b0b26..53ab43e8018 100644 --- a/packages/cli/src/utils/createFileFromTemplate.test.ts +++ b/packages/cli/src/utils/createFileFromTemplate.test.ts @@ -9,12 +9,14 @@ afterEach(() => { const preReplacement = `import { createPagesRoute } from "@trigger.dev/nextjs"; import { client } from "\${routePathPrefix}trigger"; +import { other } from "\${routePathPrefix}trigger"; import "\${anotherPathPrefix}jobs"; const { handler, config } = createPagesRoute(client);`; const postReplacement = `import { createPagesRoute } from "@trigger.dev/nextjs"; import { client } from "@/trigger"; +import { other } from "@/trigger"; import "@/src/jobs"; const { handler, config } = createPagesRoute(client);`; From 3ace90f2ece303af8a217dd4f90853e31272514b Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 14:21:52 +0100 Subject: [PATCH 14/53] Created a test for the install step (it fails right now with JS) --- .../cli/src/frameworks/nextjs/nextjs.test.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/frameworks/nextjs/nextjs.test.ts b/packages/cli/src/frameworks/nextjs/nextjs.test.ts index a691d36b226..dd0c15fee8c 100644 --- a/packages/cli/src/frameworks/nextjs/nextjs.test.ts +++ b/packages/cli/src/frameworks/nextjs/nextjs.test.ts @@ -1,5 +1,5 @@ import mock from "mock-fs"; -import { detectPagesOrAppDir, detectUseOfSrcDir } from "."; +import { NextJs, detectPagesOrAppDir, detectUseOfSrcDir } from "."; import { getFramework } from ".."; afterEach(() => { @@ -91,3 +91,17 @@ describe("detect pages or app directory", () => { expect(projectType).toEqual("pages"); }); }); + +describe("file creation", () => { + test("pages directory", async () => { + mock({ + "src/app": {}, + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("app"); + + const nextJs = new NextJs(); + await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); + }); +}); From a77d494c4e0417f5b4b50da570276d0aeb38336b Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 14:22:00 +0100 Subject: [PATCH 15/53] Removed unused import --- packages/cli/src/utils/createFileFromTemplate.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cli/src/utils/createFileFromTemplate.test.ts b/packages/cli/src/utils/createFileFromTemplate.test.ts index 53ab43e8018..a9294844974 100644 --- a/packages/cli/src/utils/createFileFromTemplate.test.ts +++ b/packages/cli/src/utils/createFileFromTemplate.test.ts @@ -1,7 +1,6 @@ +import fs from "fs/promises"; import mock from "mock-fs"; import { createFileFromTemplate, replaceAll } from "./createFileFromTemplate"; -import fs from "fs/promises"; -import exp from "constants"; afterEach(() => { mock.restore(); From 4e910297a1bdc016682cf106be2dc225a659ff5e Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 15:18:44 +0100 Subject: [PATCH 16/53] =?UTF-8?q?Another=20test=20that=20should=20pass=20b?= =?UTF-8?q?ut=20currently=20fails=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/frameworks/nextjs/nextjs.test.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/frameworks/nextjs/nextjs.test.ts b/packages/cli/src/frameworks/nextjs/nextjs.test.ts index dd0c15fee8c..3b29f184fc4 100644 --- a/packages/cli/src/frameworks/nextjs/nextjs.test.ts +++ b/packages/cli/src/frameworks/nextjs/nextjs.test.ts @@ -93,7 +93,7 @@ describe("detect pages or app directory", () => { }); describe("file creation", () => { - test("pages directory", async () => { + test("pages + javascript (without jsconfig)", async () => { mock({ "src/app": {}, }); @@ -104,4 +104,17 @@ describe("file creation", () => { const nextJs = new NextJs(); await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); }); + + test("pages + javascript (with blank jsconfig)", async () => { + mock({ + "src/app": {}, + "jsconfig.json": "{}", + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("app"); + + const nextJs = new NextJs(); + await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); + }); }); From 2149894d646d960390470ead0f28465e0013aa87 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 15:43:18 +0100 Subject: [PATCH 17/53] Path alias fixed and now has tests --- packages/cli/src/utils/pathAlias.test.ts | 133 +++++++++++++++++++++++ packages/cli/src/utils/pathAlias.ts | 51 +++++++++ 2 files changed, 184 insertions(+) create mode 100644 packages/cli/src/utils/pathAlias.test.ts create mode 100644 packages/cli/src/utils/pathAlias.ts diff --git a/packages/cli/src/utils/pathAlias.test.ts b/packages/cli/src/utils/pathAlias.test.ts new file mode 100644 index 00000000000..f28e06e8877 --- /dev/null +++ b/packages/cli/src/utils/pathAlias.test.ts @@ -0,0 +1,133 @@ +import mock from "mock-fs"; +import { getPathAlias } from "./pathAlias"; + +describe("javascript config", () => { + test("no jsconfig means no alias", async () => { + mock({ + "src/pages": {}, + }); + + const alias = await getPathAlias({ + projectPath: "", + isTypescriptProject: false, + usesSrcDir: true, + }); + + expect(alias).toBeUndefined(); + }); + + test("jsconfig without alias", async () => { + mock({ + "src/pages": {}, + "jsconfig.json": JSON.stringify({}), + }); + + const alias = await getPathAlias({ + projectPath: "", + isTypescriptProject: false, + usesSrcDir: true, + }); + + expect(alias).toBeUndefined(); + }); + + test("jsconfig without paths", async () => { + mock({ + "src/pages": {}, + "jsconfig.json": `{"compilerOptions": {}}`, + }); + + const alias = await getPathAlias({ + projectPath: "", + isTypescriptProject: false, + usesSrcDir: true, + }); + + expect(alias).toBeUndefined(); + }); + + test("jsconfig without matching alias", async () => { + mock({ + "src/pages": {}, + "jsconfig.json": `{"compilerOptions": { "paths": { + "~/*": ["./app/*"] + }}}`, + }); + + const alias = await getPathAlias({ + projectPath: "", + isTypescriptProject: false, + usesSrcDir: true, + }); + + expect(alias).toBeUndefined(); + }); + + test("jsconfig src dir", async () => { + mock({ + "src/pages": {}, + "jsconfig.json": `{"compilerOptions": { "paths": { + "~/*": ["./src/*"] + }}}`, + }); + + const alias = await getPathAlias({ + projectPath: "", + isTypescriptProject: false, + usesSrcDir: true, + }); + + expect(alias).toEqual("~"); + }); + + test("jsconfig no src dir", async () => { + mock({ + pages: {}, + "jsconfig.json": `{"compilerOptions": { "paths": { + "~/*": ["./*"] + }}}`, + }); + + const alias = await getPathAlias({ + projectPath: "", + isTypescriptProject: false, + usesSrcDir: false, + }); + + expect(alias).toEqual("~"); + }); + + test("tsconfig no src dir", async () => { + mock({ + "src/pages": {}, + "tsconfig.json": `{"compilerOptions": { "paths": { + "~/*": ["./src/*"] + }}}`, + }); + + const alias = await getPathAlias({ + projectPath: "", + isTypescriptProject: true, + usesSrcDir: true, + }); + + expect(alias).toEqual("~"); + }); + + test("tsconfig no src dir", async () => { + mock({ + pages: {}, + "tsconfig.json": `{"compilerOptions": { "paths": { + "~/*": ["./*"] + }}}`, + }); + + const alias = await getPathAlias({ + projectPath: "", + isTypescriptProject: true, + usesSrcDir: false, + }); + + expect(alias).toEqual("~"); + }); +}); diff --git a/packages/cli/src/utils/pathAlias.ts b/packages/cli/src/utils/pathAlias.ts new file mode 100644 index 00000000000..e6b67d9b96f --- /dev/null +++ b/packages/cli/src/utils/pathAlias.ts @@ -0,0 +1,51 @@ +import pathModule from "path"; +import { pathExists } from "./fileSystem"; +import { parse } from "tsconfck"; + +type Options = { projectPath: string; isTypescriptProject: boolean; usesSrcDir: boolean }; + +// Find the alias that points to the "src" directory. +// So for example, the paths object could be: +// { +// "@/*": ["./src/*"] +// } +// In this case, we would return "@" +export async function getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }: Options) { + const configFileName = isTypescriptProject ? "tsconfig.json" : "jsconfig.json"; + const tsConfigPath = pathModule.join(projectPath, configFileName); + const configFileExists = await pathExists(tsConfigPath); + + //no config and javascript, no alias + if (!isTypescriptProject && !configFileExists) { + return; + } + + const { tsconfig } = await parse(tsConfigPath); + + const paths = tsconfig?.compilerOptions?.paths; + if (paths === undefined) { + return; + } + + const alias = Object.keys(paths).find((key) => { + const value = paths[key]; + + if (value.length === 0) { + return false; + } + + const path = value[0]; + if (usesSrcDir) { + return path === "./src/*"; + } else { + return path === "./*"; + } + }); + + // Make sure to remove the trailing "/*" + if (alias) { + return alias.slice(0, -2); + } + + return; +} From d79201e9f3f700d784c9dbeb9bbf2e5a836d6d7e Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 15:43:33 +0100 Subject: [PATCH 18/53] New pathAlias function used --- packages/cli/src/frameworks/nextjs/index.ts | 46 ++----------------- .../cli/src/frameworks/nextjs/nextjs.test.ts | 26 ++++++++--- 2 files changed, 23 insertions(+), 49 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index e9463fddce5..6bb2222ed8b 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -9,6 +9,7 @@ import { pathExists } from "../../utils/fileSystem"; import { parse } from "tsconfck"; import { detectMiddlewareUsage } from "./middleware"; import { removeFileExtension } from "../../utils/removeFileExtension"; +import { getPathAlias } from "../../utils/pathAlias"; export class NextJs implements Framework { id = "nextjs"; @@ -119,11 +120,7 @@ async function createTriggerPageRoute( isTypescriptProject: boolean, usesSrcDir = false ) { - const configFileName = isTypescriptProject ? "tsconfig.json" : "jsconfig.json"; - const tsConfigPath = pathModule.join(projectPath, configFileName); - const { tsconfig } = await parse(tsConfigPath); - - const pathAlias = getPathAlias(tsconfig, usesSrcDir); + const pathAlias = getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); const routePathPrefix = pathAlias ? pathAlias + "/" : "../../"; const extension = isTypescriptProject ? ".ts" : ".js"; @@ -252,7 +249,7 @@ async function createTriggerAppRoute( const examplesIndexFileName = `index${extension}`; const routeFileName = `route${extension}`; - const pathAlias = getPathAlias(tsconfig, usesSrcDir); + const pathAlias = getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); const routePathPrefix = pathAlias ? pathAlias + "/" : "../../../"; const routeContent = ` @@ -357,40 +354,3 @@ export * from "./examples" logger.success(`βœ… Created example job at ${usesSrcDir ? "src/" : ""}jobs/examples.ts`); } } - -// Find the alias that points to the "src" directory. -// So for example, the paths object could be: -// { -// "@/*": ["./src/*"] -// } -// In this case, we would return "@" -function getPathAlias(tsconfig: any, usesSrcDir: boolean) { - if (!tsconfig.compilerOptions.paths) { - return; - } - - const paths = tsconfig.compilerOptions.paths; - - const alias = Object.keys(paths).find((key) => { - const value = paths[key]; - - if (value.length !== 1) { - return false; - } - - const path = value[0]; - - if (usesSrcDir) { - return path === "./src/*"; - } else { - return path === "./*"; - } - }); - - // Make sure to remove the trailing "/*" - if (alias) { - return alias.slice(0, -2); - } - - return; -} diff --git a/packages/cli/src/frameworks/nextjs/nextjs.test.ts b/packages/cli/src/frameworks/nextjs/nextjs.test.ts index 3b29f184fc4..162f5b5bb71 100644 --- a/packages/cli/src/frameworks/nextjs/nextjs.test.ts +++ b/packages/cli/src/frameworks/nextjs/nextjs.test.ts @@ -1,6 +1,7 @@ import mock from "mock-fs"; import { NextJs, detectPagesOrAppDir, detectUseOfSrcDir } from "."; import { getFramework } from ".."; +import { pathExists } from "../../utils/fileSystem"; afterEach(() => { mock.restore(); @@ -93,26 +94,39 @@ describe("detect pages or app directory", () => { }); describe("file creation", () => { - test("pages + javascript (without jsconfig)", async () => { + test("src/pages + javascript (without jsconfig)", async () => { mock({ - "src/app": {}, + "src/pages": {}, }); const projectType = await detectPagesOrAppDir(""); - expect(projectType).toEqual("app"); + expect(projectType).toEqual("pages"); const nextJs = new NextJs(); await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); }); - test("pages + javascript (with blank jsconfig)", async () => { + test("src/pages + javascript (with blank jsconfig)", async () => { mock({ - "src/app": {}, + "src/pages": {}, "jsconfig.json": "{}", }); const projectType = await detectPagesOrAppDir(""); - expect(projectType).toEqual("app"); + expect(projectType).toEqual("pages"); + + const nextJs = new NextJs(); + await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); + }); + + test("src/pages + javascript (with alias in jsconfig)", async () => { + mock({ + "src/pages": {}, + "jsconfig.json": `{"compilerOptions": {}}`, + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("pages"); const nextJs = new NextJs(); await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); From 1024032c835c3e95d274f45da62bf678f343fcbe Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 15:57:06 +0100 Subject: [PATCH 19/53] Nextjs page install tests --- .../cli/src/frameworks/nextjs/nextjs.test.ts | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/nextjs.test.ts b/packages/cli/src/frameworks/nextjs/nextjs.test.ts index 162f5b5bb71..3cbdfc96369 100644 --- a/packages/cli/src/frameworks/nextjs/nextjs.test.ts +++ b/packages/cli/src/frameworks/nextjs/nextjs.test.ts @@ -93,8 +93,8 @@ describe("detect pages or app directory", () => { }); }); -describe("file creation", () => { - test("src/pages + javascript (without jsconfig)", async () => { +describe("pages install", () => { + test("src/pages + javascript", async () => { mock({ "src/pages": {}, }); @@ -104,12 +104,15 @@ describe("file creation", () => { const nextJs = new NextJs(); await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("src/trigger.js")).toEqual(true); + expect(await pathExists("src/pages/api/trigger.js")).toEqual(true); + expect(await pathExists("src/jobs/index.js")).toEqual(true); + expect(await pathExists("src/jobs/examples.js")).toEqual(true); }); - test("src/pages + javascript (with blank jsconfig)", async () => { + test("pages + javascript", async () => { mock({ - "src/pages": {}, - "jsconfig.json": "{}", + pages: {}, }); const projectType = await detectPagesOrAppDir(""); @@ -117,18 +120,43 @@ describe("file creation", () => { const nextJs = new NextJs(); await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("trigger.js")).toEqual(true); + expect(await pathExists("pages/api/trigger.js")).toEqual(true); + expect(await pathExists("jobs/index.js")).toEqual(true); + expect(await pathExists("jobs/examples.js")).toEqual(true); }); - test("src/pages + javascript (with alias in jsconfig)", async () => { + test("src/pages + typescript", async () => { mock({ "src/pages": {}, - "jsconfig.json": `{"compilerOptions": {}}`, + "tsconfig.json": JSON.stringify({}), }); const projectType = await detectPagesOrAppDir(""); expect(projectType).toEqual("pages"); const nextJs = new NextJs(); - await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); + await nextJs.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("src/trigger.ts")).toEqual(true); + expect(await pathExists("src/pages/api/trigger.ts")).toEqual(true); + expect(await pathExists("src/jobs/index.ts")).toEqual(true); + expect(await pathExists("src/jobs/examples.ts")).toEqual(true); + }); + + test("pages + typescript", async () => { + mock({ + pages: {}, + "tsconfig.json": JSON.stringify({}), + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("pages"); + + const nextJs = new NextJs(); + await nextJs.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("trigger.ts")).toEqual(true); + expect(await pathExists("pages/api/trigger.ts")).toEqual(true); + expect(await pathExists("jobs/index.ts")).toEqual(true); + expect(await pathExists("jobs/examples.ts")).toEqual(true); }); }); From b1b3e64bbe2bf7fc7ad19641f2df08a25c6e645e Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 16:06:20 +0100 Subject: [PATCH 20/53] Fixed app directory install (with tests) --- packages/cli/src/frameworks/nextjs/index.ts | 5 +- .../cli/src/frameworks/nextjs/nextjs.test.ts | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 6bb2222ed8b..62f10d56864 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -239,9 +239,7 @@ async function createTriggerAppRoute( isTypescriptProject: boolean, usesSrcDir = false ) { - const configFileName = isTypescriptProject ? "tsconfig.json" : "jsconfig.json"; - const tsConfigPath = pathModule.join(projectPath, configFileName); - const { tsconfig } = await parse(tsConfigPath); + const pathAlias = getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); const extension = isTypescriptProject ? ".ts" : ".js"; const triggerFileName = `trigger${extension}`; @@ -249,7 +247,6 @@ async function createTriggerAppRoute( const examplesIndexFileName = `index${extension}`; const routeFileName = `route${extension}`; - const pathAlias = getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); const routePathPrefix = pathAlias ? pathAlias + "/" : "../../../"; const routeContent = ` diff --git a/packages/cli/src/frameworks/nextjs/nextjs.test.ts b/packages/cli/src/frameworks/nextjs/nextjs.test.ts index 3cbdfc96369..3a48c409dbc 100644 --- a/packages/cli/src/frameworks/nextjs/nextjs.test.ts +++ b/packages/cli/src/frameworks/nextjs/nextjs.test.ts @@ -160,3 +160,71 @@ describe("pages install", () => { expect(await pathExists("jobs/examples.ts")).toEqual(true); }); }); + +describe("app install", () => { + test("src/app + javascript", async () => { + mock({ + "src/app": {}, + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("app"); + + const nextJs = new NextJs(); + await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("src/trigger.js")).toEqual(true); + expect(await pathExists("src/app/api/trigger/route.js")).toEqual(true); + expect(await pathExists("src/jobs/index.js")).toEqual(true); + expect(await pathExists("src/jobs/examples.js")).toEqual(true); + }); + + test("app + javascript", async () => { + mock({ + app: {}, + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("app"); + + const nextJs = new NextJs(); + await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("trigger.js")).toEqual(true); + expect(await pathExists("app/api/trigger/route.js")).toEqual(true); + expect(await pathExists("jobs/index.js")).toEqual(true); + expect(await pathExists("jobs/examples.js")).toEqual(true); + }); + + test("src/app + typescript", async () => { + mock({ + "src/app": {}, + "tsconfig.json": JSON.stringify({}), + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("app"); + + const nextJs = new NextJs(); + await nextJs.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("src/trigger.ts")).toEqual(true); + expect(await pathExists("src/app/api/trigger/route.ts")).toEqual(true); + expect(await pathExists("src/jobs/index.ts")).toEqual(true); + expect(await pathExists("src/jobs/examples.ts")).toEqual(true); + }); + + test("app + typescript", async () => { + mock({ + app: {}, + "tsconfig.json": JSON.stringify({}), + }); + + const projectType = await detectPagesOrAppDir(""); + expect(projectType).toEqual("app"); + + const nextJs = new NextJs(); + await nextJs.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("trigger.ts")).toEqual(true); + expect(await pathExists("app/api/trigger/route.ts")).toEqual(true); + expect(await pathExists("jobs/index.ts")).toEqual(true); + expect(await pathExists("jobs/examples.ts")).toEqual(true); + }); +}); From abdaf3ac1c227fda9f9f68dd5aab826afe1808c5 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 16:07:47 +0100 Subject: [PATCH 21/53] Removed e2e CLI test, switched to unit testing strategy instead --- packages/cli/package.json | 1 - packages/cli/test/example.test.ts | 43 ------------------------------- 2 files changed, 44 deletions(-) delete mode 100644 packages/cli/test/example.test.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 89e6a3b26ca..5c9fbe3835d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,7 +35,6 @@ "trigger-cli": "./dist/index.js" }, "devDependencies": { - "@gmrchk/cli-testing-library": "^0.1.2", "@trigger.dev/tsconfig": "workspace:*", "@types/gradient-string": "^1.1.2", "@types/inquirer": "^9.0.3", diff --git a/packages/cli/test/example.test.ts b/packages/cli/test/example.test.ts deleted file mode 100644 index 18ec80513c7..00000000000 --- a/packages/cli/test/example.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { prepareEnvironment } from "@gmrchk/cli-testing-library"; -import { CLITestEnvironment } from "@gmrchk/cli-testing-library/lib/types"; -import { join } from "node:path"; - -let environment: CLITestEnvironment; - -beforeAll(async () => { - // This will create a "sandbox" terminal under `/var/folders` - environment = await prepareEnvironment(); -}); - -afterAll(async () => { - await environment.cleanup(); -}); - -// this test is not returning timeout -describe.skip('cli', () => { - // can be any path with a nextjs project - const NEXT_PROJECT_PATH = join(__dirname, '..', '..', '..', 'examples', 'nextjs-example'); - - it('should be able to execute cli', async () => { - const { waitForText, getStdout, wait, pressKey } = await environment.spawn('node', `${join(__dirname, '..', 'dist', 'index.js')} init -p ${NEXT_PROJECT_PATH}`) - - console.log('getStdout() :>> ', getStdout()); - - // this promises never resolves - // maybe we have a conflict between vitest and @gmrchk/cli-testing-library? - // with jest works fine, but with vitest not - await waitForText('Detected Next.js project'); - - console.log('getStdout() :>> ', getStdout()); - - await waitForText('Are you using the Trigger.dev cloud or self-hosted?'); - - console.log('getStdout() :>> ', getStdout()); - - await pressKey('enter'); - - console.log('getStdout() :>> ', getStdout()); - - // wait next prompt, make assertions and keep going - }); -}, 20000) \ No newline at end of file From ac10c35139954e797adb332feb232099f97d7980 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 15 Sep 2023 16:07:52 +0100 Subject: [PATCH 22/53] Latest lockfile --- pnpm-lock.yaml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 152e846efc9..03257a02846 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -932,7 +932,6 @@ importers: packages/cli: specifiers: - '@gmrchk/cli-testing-library': ^0.1.2 '@trigger.dev/tsconfig': workspace:* '@types/degit': ^2.8.3 '@types/gradient-string': ^1.1.2 @@ -995,7 +994,6 @@ importers: url: 0.11.1 zod: 3.21.4 devDependencies: - '@gmrchk/cli-testing-library': 0.1.2 '@trigger.dev/tsconfig': link:../../config-packages/tsconfig '@types/gradient-string': 1.1.2 '@types/inquirer': 9.0.3 @@ -6013,12 +6011,6 @@ packages: resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} dev: true - /@gmrchk/cli-testing-library/0.1.2: - resolution: {integrity: sha512-/oALX+4Zti+92hJaR7trZlDPzI2EekIHEYoDXH3oGTdCdgq5lDT3vHAMDzTjbIfUA0X/XC8sVA2tbtSoUPB5xw==} - dependencies: - keycode: 2.2.1 - dev: true - /@godaddy/terminus/4.12.1: resolution: {integrity: sha512-Tm+wVu1/V37uZXcT7xOhzdpFoovQReErff8x3y82k6YyWa1gzxWBjTyrx4G2enjEqoXPnUUmJ3MOmwH+TiP6Sw==} dependencies: @@ -21391,10 +21383,6 @@ packages: safe-buffer: 5.2.1 dev: false - /keycode/2.2.1: - resolution: {integrity: sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==} - dev: true - /keyv/3.1.0: resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} dependencies: From 3dd9e5532870d6d8f2787b741c2ff9e2618b8bc6 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sat, 16 Sep 2023 11:28:42 +0100 Subject: [PATCH 23/53] The install files are now actual files that are copied and transformed --- packages/cli/src/commands/init.ts | 17 +- .../src/frameworks/nextjs/files/exampleJob.js | 27 + .../src/frameworks/nextjs/files/jobsIndex.js | 3 + .../src/frameworks/nextjs/files/trigger.js | 7 + packages/cli/src/frameworks/nextjs/index.ts | 176 ++--- .../src/utils/createFileFromTemplate.test.ts | 3 +- .../cli/src/utils/createFileFromTemplate.ts | 8 +- pnpm-lock.yaml | 640 +++++++++++++++++- 8 files changed, 747 insertions(+), 134 deletions(-) create mode 100644 packages/cli/src/frameworks/nextjs/files/exampleJob.js create mode 100644 packages/cli/src/frameworks/nextjs/files/jobsIndex.js create mode 100644 packages/cli/src/frameworks/nextjs/files/trigger.js diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 0b7166886a0..db3208656da 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -3,27 +3,24 @@ import fs from "fs/promises"; import inquirer from "inquirer"; import pathModule from "path"; -import { pathToRegexp } from "path-to-regexp"; import { simpleGit } from "simple-git"; -import { parse } from "tsconfck"; - import { promptApiKey, promptTriggerUrl } from "../cli/index"; import { CLOUD_API_URL, CLOUD_TRIGGER_URL, COMMAND_NAME } from "../consts"; +import { frameworkNames, getFramework } from "../frameworks"; import { telemetryClient } from "../telemetry/telemetry"; import { addDependencies } from "../utils/addDependencies"; -import { pathExists, readJSONFile } from "../utils/fileSystem"; -import { logger } from "../utils/logger"; -import { resolvePath } from "../utils/parseNameAndPath"; -import { renderTitle } from "../utils/renderTitle"; -import { TriggerApi, WhoamiResponse } from "../utils/triggerApi"; -import { frameworkNames, getFramework } from "../frameworks"; -import { getUserPackageManager } from "../utils/getUserPkgManager"; import { getEnvFilename, setApiKeyEnvironmentVariable, setApiUrlEnvironmentVariable, } from "../utils/env"; +import { readJSONFile } from "../utils/fileSystem"; +import { getUserPackageManager } from "../utils/getUserPkgManager"; +import { logger } from "../utils/logger"; +import { resolvePath } from "../utils/parseNameAndPath"; import { readPackageJson } from "../utils/readPackageJson"; +import { renderTitle } from "../utils/renderTitle"; +import { TriggerApi, WhoamiResponse } from "../utils/triggerApi"; export type InitCommandOptions = { projectPath: string; diff --git a/packages/cli/src/frameworks/nextjs/files/exampleJob.js b/packages/cli/src/frameworks/nextjs/files/exampleJob.js new file mode 100644 index 00000000000..ff0fbdc800e --- /dev/null +++ b/packages/cli/src/frameworks/nextjs/files/exampleJob.js @@ -0,0 +1,27 @@ +import { eventTrigger } from "@trigger.dev/sdk"; +import { client } from "${jobsPathPrefix}trigger"; + +// Your first job +// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline +client.defineJob({ + // This is the unique identifier for your Job, it must be unique across all Jobs in your project + id: "example-job", + name: "Example Job: a joke with a delay", + version: "0.0.1", + // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + // This logs a message to the console + await io.logger.info("πŸ§ͺ Example Job: a joke with a delay"); + await io.logger.info("How do you comfort a JavaScript bug?"); + // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year + await io.wait("Wait 5 seconds for the punchline...", 5); + await io.logger.info("You console it! 🀦"); + await io.logger.info( + "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" + ); + // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job + }, +}); diff --git a/packages/cli/src/frameworks/nextjs/files/jobsIndex.js b/packages/cli/src/frameworks/nextjs/files/jobsIndex.js new file mode 100644 index 00000000000..042a4e5828a --- /dev/null +++ b/packages/cli/src/frameworks/nextjs/files/jobsIndex.js @@ -0,0 +1,3 @@ +// export all your job files here + +export * from "./examples"; diff --git a/packages/cli/src/frameworks/nextjs/files/trigger.js b/packages/cli/src/frameworks/nextjs/files/trigger.js new file mode 100644 index 00000000000..578cfb089b9 --- /dev/null +++ b/packages/cli/src/frameworks/nextjs/files/trigger.js @@ -0,0 +1,7 @@ +import { TriggerClient } from "@trigger.dev/sdk"; + +export const client = new TriggerClient({ + id: "${endpointSlug}", + apiKey: process.env.TRIGGER_API_KEY, + apiUrl: process.env.TRIGGER_API_URL, +}); diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 62f10d56864..970c98d2189 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -1,7 +1,6 @@ import { Framework } from ".."; import { InstallPackage } from "../../utils/addDependencies"; import { PackageManager } from "../../utils/getUserPkgManager"; -import fs from "fs/promises"; import pathModule from "path"; import { readPackageJson } from "../../utils/readPackageJson"; import { logger } from "../../utils/logger"; @@ -10,6 +9,10 @@ import { parse } from "tsconfck"; import { detectMiddlewareUsage } from "./middleware"; import { removeFileExtension } from "../../utils/removeFileExtension"; import { getPathAlias } from "../../utils/pathAlias"; +import { createFileFromTemplate } from "../../utils/createFileFromTemplate"; +import fs from "fs/promises"; +import path from "path"; +import { fileURLToPath } from "url"; export class NextJs implements Framework { id = "nextjs"; @@ -29,6 +32,7 @@ export class NextJs implements Framework { return [ { name: "@trigger.dev/sdk", tag: "latest" }, { name: "@trigger.dev/nextjs", tag: "latest" }, + { name: "@trigger.dev/react", tag: "latest" }, ]; } @@ -120,116 +124,84 @@ async function createTriggerPageRoute( isTypescriptProject: boolean, usesSrcDir = false ) { - const pathAlias = getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); - const routePathPrefix = pathAlias ? pathAlias + "/" : "../../"; - - const extension = isTypescriptProject ? ".ts" : ".js"; - const triggerFileName = `trigger${extension}`; - const examplesFileName = `examples${extension}`; - const examplesIndexFileName = `index${extension}`; - - const routeContent = ` -import { createPagesRoute } from "@trigger.dev/nextjs"; -import { client } from "${routePathPrefix}trigger"; - -import "${routePathPrefix}jobs"; - -//this route is used to send and receive data with Trigger.dev -const { handler, config } = createPagesRoute(client); -export { config }; - -export default handler; - `; - - const triggerContent = ` -import { TriggerClient } from "@trigger.dev/sdk"; - -export const client = new TriggerClient({ - id: "${endpointSlug}", - apiKey: process.env.TRIGGER_API_KEY, - apiUrl: process.env.TRIGGER_API_URL, -}); - `; - - const jobsPathPrefix = pathAlias ? pathAlias + "/" : "../"; - - const jobsContent = ` -import { eventTrigger } from "@trigger.dev/sdk"; -import { client } from "${jobsPathPrefix}trigger"; - -// Your first job -// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline -client.defineJob({ - // This is the unique identifier for your Job, it must be unique across all Jobs in your project - id: "example-job", - name: "Example Job: a joke with a delay", - version: "0.0.1", - // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction - trigger: eventTrigger({ - name: "example.event", - }), - run: async (payload, io, ctx) => { - // This logs a message to the console - await io.logger.info("πŸ§ͺ Example Job: a joke with a delay"); - await io.logger.info("How do you comfort a JavaScript bug?"); - // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year - await io.wait("Wait 5 seconds for the punchline...", 5); - await io.logger.info("You console it! 🀦"); - await io.logger.info( - "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" - ); - // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job - }, -}); -`; - - const examplesIndexContent = ` -// import all your job files here + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); -export * from "./examples" - `; - - const directories = pathModule.join(path, "pages", "api"); - await fs.mkdir(directories, { recursive: true }); - - // Don't overwrite the file if it already exists - const exists = await pathExists(pathModule.join(directories, triggerFileName)); - - if (exists) { - logger.info("Skipping creation of pages route because it already exists"); - return; + const pathAlias = getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); + const fileExtension = isTypescriptProject ? ".ts" : ".js"; + + //pages/api/trigger.js or src/pages/api/trigger.js + const apiRoutePath = pathModule.join(path, "pages", "api", `trigger${fileExtension}`); + + //todo load the file from a file that is copied by tsup + //e.g. templates/nextjs/whatever.js + //load the file using the + + const apiRouteResult = await createFileFromTemplate({ + template: apiRouteTemplate, + replacements: { + routePathPrefix: pathAlias ? pathAlias + "/" : "../../", + }, + outputPath: apiRoutePath, + }); + if (!apiRouteResult.success) { + throw new Error("Failed to create API route file"); } + logger.success(`βœ… Created API route at ${apiRoutePath}`); - await fs.writeFile(pathModule.join(directories, triggerFileName), routeContent); - logger.success( - `βœ… Created pages route at ${usesSrcDir ? "src/" : ""}pages/api/${triggerFileName}` + //trigger.js or src/trigger.js + const triggerFilePath = pathModule.join(path, `trigger${fileExtension}`); + const triggerTemplate = await readFileIgnoringMock( + pathModule.join(__dirname, "files", "trigger.js") ); - - const triggerFileExists = await pathExists(pathModule.join(path, triggerFileName)); - - if (!triggerFileExists) { - await fs.writeFile(pathModule.join(path, triggerFileName), triggerContent); - - logger.success(`βœ… Created TriggerClient at ${usesSrcDir ? "src/" : ""}${triggerFileName}`); + const triggerResult = await createFileFromTemplate({ + template: triggerTemplate, + replacements: { + endpointSlug, + }, + outputPath: triggerFilePath, + }); + if (!triggerResult.success) { + throw new Error("Failed to create trigger file"); } + logger.success(`βœ… Created Trigger client at ${triggerFilePath}`); - const exampleDirectories = pathModule.join(path, "jobs"); - await fs.mkdir(exampleDirectories, { recursive: true }); - - const exampleFileExists = await pathExists(pathModule.join(exampleDirectories, examplesFileName)); + //example jobs + const exampleDirectory = pathModule.join(path, "jobs"); - if (!exampleFileExists) { - await fs.writeFile(pathModule.join(exampleDirectories, examplesFileName), jobsContent); - - await fs.writeFile( - pathModule.join(exampleDirectories, examplesIndexFileName), - examplesIndexContent - ); + //jobs/examples.js or src/jobs/examples.js + const exampleJobFilePath = pathModule.join(exampleDirectory, `examples${fileExtension}`); + const exampleJobTemplate = await readFileIgnoringMock( + pathModule.join(__dirname, "files", "exampleJob.js") + ); + const exampleJobResult = await createFileFromTemplate({ + template: exampleJobTemplate, + replacements: { + jobsPathPrefix: pathAlias ? pathAlias + "/" : "../", + }, + outputPath: exampleJobFilePath, + }); + if (!exampleJobResult.success) { + throw new Error("Failed to create example job file"); + } + logger.success(`βœ… Created example job at ${exampleJobFilePath}`); - logger.success( - `βœ… Created example job at ${usesSrcDir ? "src/" : ""}jobs/examples/${examplesFileName}` - ); + //jobs/index.js or src/jobs/index.js + const jobsIndexFilePath = pathModule.join(exampleDirectory, `index${fileExtension}`); + const jobsIndexTemplate = await readFileIgnoringMock( + pathModule.join(__dirname, "files", "jobsIndex.js") + ); + const jobsIndexResult = await createFileFromTemplate({ + template: jobsIndexTemplate, + replacements: { + jobsPathPrefix: pathAlias ? pathAlias + "/" : "../", + }, + outputPath: jobsIndexFilePath, + }); + if (!jobsIndexResult.success) { + throw new Error("Failed to create jobs index file"); } + logger.success(`βœ… Created jobs index at ${jobsIndexFilePath}`); } async function createTriggerAppRoute( diff --git a/packages/cli/src/utils/createFileFromTemplate.test.ts b/packages/cli/src/utils/createFileFromTemplate.test.ts index a9294844974..b9468634a77 100644 --- a/packages/cli/src/utils/createFileFromTemplate.test.ts +++ b/packages/cli/src/utils/createFileFromTemplate.test.ts @@ -38,8 +38,9 @@ describe("Template files", () => { }, }); + const template = await fs.readFile("templates/some-file.js", "utf-8"); const result = await createFileFromTemplate({ - templatePath: "templates/some-file.js", + template, replacements: { routePathPrefix: "@/", anotherPathPrefix: "@/src/", diff --git a/packages/cli/src/utils/createFileFromTemplate.ts b/packages/cli/src/utils/createFileFromTemplate.ts index 29e8e25074a..da36cdc5c19 100644 --- a/packages/cli/src/utils/createFileFromTemplate.ts +++ b/packages/cli/src/utils/createFileFromTemplate.ts @@ -13,7 +13,7 @@ type Result = }; export async function createFileFromTemplate(params: { - templatePath: string; + template: string; replacements: Record; outputPath: string; }): Promise { @@ -25,13 +25,15 @@ export async function createFileFromTemplate(params: { } try { - const template = await fs.readFile(params.templatePath, "utf-8"); - const output = replaceAll(template, params.replacements); + const output = replaceAll(params.template, params.replacements); const directoryName = path.dirname(params.outputPath); + console.log(`Creating directory ${directoryName}`); await fs.mkdir(directoryName, { recursive: true }); await fs.writeFile(params.outputPath, output); + console.log(`Created file ${params.outputPath}`); + return { success: true, alreadyExisted: false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03257a02846..2d2aeda5fae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -631,6 +631,43 @@ importers: '@types/react-dom': 18.2.7 concurrently: 8.2.0 + examples/pages-app: + specifiers: + '@trigger.dev/cli': workspace:* + '@trigger.dev/nextjs': ^2.1.3 + '@trigger.dev/react': ^2.1.3 + '@trigger.dev/sdk': ^2.1.3 + '@types/node': 20.6.1 + '@types/react': 18.2.21 + '@types/react-dom': 18.2.7 + autoprefixer: 10.4.15 + eslint: 8.49.0 + eslint-config-next: 13.4.19 + next: 13.4.19 + postcss: 8.4.29 + react: 18.2.0 + react-dom: 18.2.0 + tailwindcss: 3.3.3 + typescript: 5.2.2 + dependencies: + '@trigger.dev/nextjs': 2.1.3_tldnt3a5zjswfruihdwjxwqg4i + '@trigger.dev/react': 2.1.3_biqbaboplfbrettd7655fr4n2y + '@trigger.dev/sdk': 2.1.3 + '@types/node': 20.6.1 + '@types/react': 18.2.21 + '@types/react-dom': 18.2.7 + autoprefixer: 10.4.15_postcss@8.4.29 + eslint: 8.49.0 + eslint-config-next: 13.4.19_rngtr6f3b25lvetpihwplgecf4 + next: 13.4.19_biqbaboplfbrettd7655fr4n2y + postcss: 8.4.29 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + tailwindcss: 3.3.3 + typescript: 5.2.2 + devDependencies: + '@trigger.dev/cli': link:../../packages/cli + examples/remix-example: specifiers: '@remix-run/css-bundle': ^1.19.3 @@ -946,6 +983,7 @@ importers: dotenv: ^16.3.1 execa: ^7.0.0 gradient-string: ^2.0.2 + import-as-string: ^1.0.0 inquirer: ^9.1.4 localtunnel: ^2.0.2 mock-fs: ^5.2.0 @@ -977,8 +1015,10 @@ importers: dotenv: 16.3.1 execa: 7.0.0 gradient-string: 2.0.2 + import-as-string: 1.0.0 inquirer: 9.1.4 localtunnel: 2.0.2 + mock-fs: 5.2.0 nanoid: 4.0.2 ngrok: 5.0.0-beta.2 node-fetch: 3.3.0 @@ -1000,7 +1040,6 @@ importers: '@types/mock-fs': 4.13.1 '@types/node': 16.18.11 '@types/node-fetch': 2.6.2 - mock-fs: 5.2.0 rimraf: 3.0.2 tsup: 6.6.3_typescript@4.9.5 type-fest: 3.6.0 @@ -5917,10 +5956,25 @@ packages: eslint: 8.45.0 eslint-visitor-keys: 3.4.2 + /@eslint-community/eslint-utils/4.4.0_eslint@8.49.0: + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.49.0 + eslint-visitor-keys: 3.4.2 + dev: false + /@eslint-community/regexpp/4.5.1: resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + /@eslint-community/regexpp/4.8.1: + resolution: {integrity: sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: false + /@eslint/eslintrc/1.4.1: resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5954,6 +6008,23 @@ packages: transitivePeerDependencies: - supports-color + /@eslint/eslintrc/2.1.2: + resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.19.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: false + /@eslint/js/8.42.0: resolution: {integrity: sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5963,6 +6034,11 @@ packages: resolution: {integrity: sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@eslint/js/8.49.0: + resolution: {integrity: sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: false + /@fal-works/esbuild-plugin-global-externals/2.1.2: resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} dev: true @@ -6193,6 +6269,17 @@ packages: transitivePeerDependencies: - supports-color + /@humanwhocodes/config-array/0.11.11: + resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: false + /@humanwhocodes/config-array/0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} @@ -6709,6 +6796,10 @@ packages: resolution: {integrity: sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==} dev: false + /@next/env/13.4.19: + resolution: {integrity: sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==} + dev: false + /@next/eslint-plugin-next/12.3.4: resolution: {integrity: sha512-BFwj8ykJY+zc1/jWANsDprDIu2MgwPOIKxNVnrKvPs+f5TPegrVnem8uScND+1veT4B7F6VeqgaNLFW1Hzl9Og==} dependencies: @@ -6727,6 +6818,12 @@ packages: glob: 7.1.7 dev: false + /@next/eslint-plugin-next/13.4.19: + resolution: {integrity: sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==} + dependencies: + glob: 7.1.7 + dev: false + /@next/eslint-plugin-next/13.4.6: resolution: {integrity: sha512-bPigeu0RI7bgy1ucBA2Yqcfg539y0Lzo38P2hIkrRB1GNvFSbYg6RTu8n6tGqPVrH3TTlPTNKLXG01wc+5NuwQ==} dependencies: @@ -6786,6 +6883,15 @@ packages: dev: false optional: true + /@next/swc-darwin-arm64/13.4.19: + resolution: {integrity: sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-darwin-x64/12.3.4: resolution: {integrity: sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==} engines: {node: '>= 10'} @@ -6821,6 +6927,15 @@ packages: dev: false optional: true + /@next/swc-darwin-x64/13.4.19: + resolution: {integrity: sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-freebsd-x64/12.3.4: resolution: {integrity: sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==} engines: {node: '>= 10'} @@ -6874,6 +6989,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-gnu/13.4.19: + resolution: {integrity: sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-musl/12.3.4: resolution: {integrity: sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==} engines: {node: '>= 10'} @@ -6909,6 +7033,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-musl/13.4.19: + resolution: {integrity: sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-gnu/12.3.4: resolution: {integrity: sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==} engines: {node: '>= 10'} @@ -6944,6 +7077,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-gnu/13.4.19: + resolution: {integrity: sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-musl/12.3.4: resolution: {integrity: sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==} engines: {node: '>= 10'} @@ -6979,6 +7121,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-musl/13.4.19: + resolution: {integrity: sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-arm64-msvc/12.3.4: resolution: {integrity: sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==} engines: {node: '>= 10'} @@ -7014,6 +7165,15 @@ packages: dev: false optional: true + /@next/swc-win32-arm64-msvc/13.4.19: + resolution: {integrity: sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-ia32-msvc/12.3.4: resolution: {integrity: sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==} engines: {node: '>= 10'} @@ -7049,6 +7209,15 @@ packages: dev: false optional: true + /@next/swc-win32-ia32-msvc/13.4.19: + resolution: {integrity: sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-x64-msvc/12.3.4: resolution: {integrity: sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==} engines: {node: '>= 10'} @@ -7084,6 +7253,15 @@ packages: dev: false optional: true + /@next/swc-win32-x64-msvc/13.4.19: + resolution: {integrity: sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@nicolo-ribaudo/eslint-scope-5-internals/5.1.1-v1: resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} dependencies: @@ -11723,6 +11901,24 @@ packages: resolution: {integrity: sha512-VGq/H3PuRoj0shOcg1S5Flv3YD2qNz2ttk8w5xe5AHQE1I8NO9EHSBUxezIpk4dD6M7bQDtwHBMqqU2EwMwyUw==} dev: false + /@tanstack/react-query/5.0.0-beta.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-JdK1HRw20tuwg3GfT3QZTkuS7s2KDa9FeozuJ7jZULlwPczZagouqYmM6+PL0ad6jfCnw8NzmLFtZdlBx6cTmA==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@tanstack/query-core': 5.0.0-beta.0 + client-only: 0.0.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /@tanstack/react-query/5.0.0-beta.2_react@18.2.0: resolution: {integrity: sha512-JdK1HRw20tuwg3GfT3QZTkuS7s2KDa9FeozuJ7jZULlwPczZagouqYmM6+PL0ad6jfCnw8NzmLFtZdlBx6cTmA==} peerDependencies: @@ -11817,6 +12013,15 @@ packages: zod-error: 1.5.0 dev: false + /@trigger.dev/core/2.1.3: + resolution: {integrity: sha512-NIqKdCyKbHz6GCCmBKNKnHMfmzAUFfw5gDs2L9c0I1jG6eVU+eUwX3MfPV3pkjWgwxhF59N5V10sX8RkFvAMTw==} + engines: {node: '>=16.8.0'} + dependencies: + ulid: 2.3.0 + zod: 3.21.4 + zod-error: 1.5.0 + dev: false + /@trigger.dev/nextjs/1.0.0_fy3ffmrhk4zoekjz4cwhclpq64: resolution: {integrity: sha512-Dt32BaAKYNJqYbPcpdzUOfkgpDRIftyd4oj2lBFilIHZUCJpfG1MdYbr4YWFhNE5Z04Pj0I+uN8kaxTkTZp+Ig==} engines: {node: '>=16.8.0'} @@ -11831,6 +12036,36 @@ packages: - supports-color dev: false + /@trigger.dev/nextjs/2.1.3_tldnt3a5zjswfruihdwjxwqg4i: + resolution: {integrity: sha512-1WdmuiCa88bpoNxgOKmmEUBmx9S+ujiGC4yQa1GVKpU6vrkPDMGbO96Yrkxlgq6FFgpOvn5DFbnT32nsn1DWiA==} + engines: {node: '>=16.8.0'} + peerDependencies: + '@trigger.dev/sdk': ^2.1.3 + next: '>=12.0.0 <14.0.0' + dependencies: + '@trigger.dev/sdk': 2.1.3 + debug: 4.3.4 + next: 13.4.19_biqbaboplfbrettd7655fr4n2y + transitivePeerDependencies: + - supports-color + dev: false + + /@trigger.dev/react/2.1.3_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-OKYK40WmHLF1FGNCBe4mCRLY0Sgc6igNjXGiF8hXKG07PkPejKXa3gt5BInrd5RPIvFIYMmjqzhna/8GEMaDPg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18 + dependencies: + '@tanstack/react-query': 5.0.0-beta.2_biqbaboplfbrettd7655fr4n2y + '@trigger.dev/core': 2.1.3 + debug: 4.3.4 + react: 18.2.0 + zod: 3.21.4 + transitivePeerDependencies: + - react-dom + - react-native + - supports-color + dev: false + /@trigger.dev/sdk/2.0.7: resolution: {integrity: sha512-40wpHx38opv2roJDz7CP4ZJYp6nMYJWLgehVjCqcvQWagcE00D6mTu3TZyFwBU8C/fy0QmdnnLehhruZMnpGpA==} engines: {node: '>=16.8.0'} @@ -11858,6 +12093,33 @@ packages: - utf-8-validate dev: false + /@trigger.dev/sdk/2.1.3: + resolution: {integrity: sha512-555TX4fvDaYdevYLGCH9U2xQXP1zCBJpbAUHnrsSAukpGynNFD+pa6qX2WXHk9IHIC7Q8DTY/PO1y9gxNVHjBg==} + engines: {node: '>=16.8.0'} + dependencies: + '@trigger.dev/core': 2.1.3 + chalk: 5.3.0 + cronstrue: 2.21.0 + debug: 4.3.4 + evt: 2.4.13 + get-caller-file: 2.0.5 + git-remote-origin-url: 4.0.0 + git-repo-info: 2.1.1 + node-fetch: 2.6.12 + slug: 6.1.0 + terminal-link: 3.0.0 + ulid: 2.3.0 + uuid: 9.0.0 + ws: 8.12.0 + zod: 3.21.4 + zod-error: 1.5.0 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: false + /@tsconfig/node10/1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} @@ -12199,7 +12461,7 @@ packages: /@types/ioredis/4.28.10: resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==} dependencies: - '@types/node': 20.6.0 + '@types/node': 20.6.1 dev: false /@types/is-ci/3.0.0: @@ -12262,7 +12524,7 @@ packages: /@types/jsonwebtoken/9.0.1: resolution: {integrity: sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==} dependencies: - '@types/node': 20.6.0 + '@types/node': 20.6.1 dev: false /@types/keygrip/1.0.2: @@ -12438,6 +12700,10 @@ packages: /@types/node/20.6.0: resolution: {integrity: sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==} + /@types/node/20.6.1: + resolution: {integrity: sha512-4LcJvuXQlv4lTHnxwyHQZ3uR9Zw2j7m1C9DfuwoTFQQP4Pmu04O6IfLYgMmHoOCt0nosItLLZAH+sOrRE0Bo8g==} + dev: false + /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -12809,6 +13075,26 @@ packages: - supports-color dev: true + /@typescript-eslint/parser/5.59.6_rngtr6f3b25lvetpihwplgecf4: + resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.59.6 + '@typescript-eslint/types': 5.59.6 + '@typescript-eslint/typescript-estree': 5.59.6_typescript@5.2.2 + debug: 4.3.4 + eslint: 8.49.0 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: false + /@typescript-eslint/scope-manager/5.59.6: resolution: {integrity: sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -12963,6 +13249,27 @@ packages: transitivePeerDependencies: - supports-color + /@typescript-eslint/typescript-estree/5.59.6_typescript@5.2.2: + resolution: {integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.59.6 + '@typescript-eslint/visitor-keys': 5.59.6 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + tsutils: 3.21.0_typescript@5.2.2 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: false + /@typescript-eslint/utils/5.59.6_eslint@8.45.0: resolution: {integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -14006,6 +14313,22 @@ packages: postcss-value-parser: 4.2.0 dev: false + /autoprefixer/10.4.15_postcss@8.4.29: + resolution: {integrity: sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.10 + caniuse-lite: 1.0.30001532 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.29 + postcss-value-parser: 4.2.0 + dev: false + /available-typed-arrays/1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} @@ -14025,7 +14348,7 @@ packages: /axios/0.21.4_debug@4.3.2: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2_debug@4.3.2 + follow-redirects: 1.15.2 transitivePeerDependencies: - debug dev: false @@ -14776,6 +15099,10 @@ packages: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} dev: false + /callsite/1.0.0: + resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} + dev: false + /callsites/3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -16935,6 +17262,31 @@ packages: - supports-color dev: false + /eslint-config-next/13.4.19_rngtr6f3b25lvetpihwplgecf4: + resolution: {integrity: sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@next/eslint-plugin-next': 13.4.19 + '@rushstack/eslint-patch': 1.2.0 + '@typescript-eslint/parser': 5.59.6_rngtr6f3b25lvetpihwplgecf4 + eslint: 8.49.0 + eslint-import-resolver-node: 0.3.7 + eslint-import-resolver-typescript: 3.5.5_3gbtcjnbjinsubg5cwv3525lbq + eslint-plugin-import: 2.27.5_tyljvsdz2ipp3eruvtbrjckloe + eslint-plugin-jsx-a11y: 6.7.1_eslint@8.49.0 + eslint-plugin-react: 7.32.2_eslint@8.49.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.49.0 + typescript: 5.2.2 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - supports-color + dev: false + /eslint-config-next/13.4.6_binxsscxvozjxebftqdoazsxm4: resolution: {integrity: sha512-nlv4FYish1RYYHILbQwM5/rD37cOvEqtMfDjtQCYbXdE2O3MggqHu2q6IDeLE2Z6u8ZJyNPgWOA6OimWcxj3qw==} peerDependencies: @@ -17054,6 +17406,30 @@ packages: - supports-color dev: true + /eslint-import-resolver-typescript/3.5.5_3gbtcjnbjinsubg5cwv3525lbq: + resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + dependencies: + debug: 4.3.4 + enhanced-resolve: 5.13.0 + eslint: 8.49.0 + eslint-module-utils: 2.7.4_bytowunmwsvn7w25lnp5m6zpem + eslint-plugin-import: 2.27.5_tyljvsdz2ipp3eruvtbrjckloe + get-tsconfig: 4.5.0 + globby: 13.1.3 + is-core-module: 2.11.0 + is-glob: 4.0.3 + synckit: 0.8.5 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + dev: false + /eslint-import-resolver-typescript/3.5.5_bsgoee2ktd7nzirwexa3gasa7m: resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -17155,6 +17531,36 @@ packages: - supports-color dev: true + /eslint-module-utils/2.7.4_bytowunmwsvn7w25lnp5m6zpem: + resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.6_rngtr6f3b25lvetpihwplgecf4 + debug: 3.2.7 + eslint: 8.49.0 + eslint-import-resolver-node: 0.3.7 + eslint-import-resolver-typescript: 3.5.5_3gbtcjnbjinsubg5cwv3525lbq + transitivePeerDependencies: + - supports-color + dev: false + /eslint-module-utils/2.7.4_hhwxwo5vg2mzr5fq3eei7kqaye: resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} engines: {node: '>=4'} @@ -17376,6 +17782,39 @@ packages: - supports-color dev: true + /eslint-plugin-import/2.27.5_tyljvsdz2ipp3eruvtbrjckloe: + resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.6_rngtr6f3b25lvetpihwplgecf4 + array-includes: 3.1.6 + array.prototype.flat: 1.3.1 + array.prototype.flatmap: 1.3.1 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.49.0 + eslint-import-resolver-node: 0.3.7 + eslint-module-utils: 2.7.4_bytowunmwsvn7w25lnp5m6zpem + has: 1.0.3 + is-core-module: 2.11.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.6 + resolve: 1.22.2 + semver: 6.3.0 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: false + /eslint-plugin-import/2.27.5_xpt3ce3kmhzhmuk3dcuwe6u2pe: resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} @@ -17573,6 +18012,31 @@ packages: object.fromentries: 2.0.6 semver: 6.3.0 + /eslint-plugin-jsx-a11y/6.7.1_eslint@8.49.0: + resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + '@babel/runtime': 7.22.5 + aria-query: 5.1.3 + array-includes: 3.1.6 + array.prototype.flatmap: 1.3.1 + ast-types-flow: 0.0.7 + axe-core: 4.6.2 + axobject-query: 3.1.1 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 8.49.0 + has: 1.0.3 + jsx-ast-utils: 3.3.3 + language-tags: 1.0.5 + minimatch: 3.1.2 + object.entries: 1.1.6 + object.fromentries: 2.0.6 + semver: 6.3.0 + dev: false + /eslint-plugin-node/11.1.0_eslint@8.31.0: resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} engines: {node: '>=8.10.0'} @@ -17639,6 +18103,15 @@ packages: eslint: 8.45.0 dev: true + /eslint-plugin-react-hooks/4.6.0_eslint@8.49.0: + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.49.0 + dev: false + /eslint-plugin-react-hooks/5.0.0-canary-7118f5dd7-20230705_eslint@8.45.0: resolution: {integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==} engines: {node: '>=10'} @@ -17766,6 +18239,30 @@ packages: semver: 6.3.0 string.prototype.matchall: 4.0.8 + /eslint-plugin-react/7.32.2_eslint@8.49.0: + resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + array-includes: 3.1.6 + array.prototype.flatmap: 1.3.1 + array.prototype.tosorted: 1.1.1 + doctrine: 2.1.0 + eslint: 8.49.0 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.3 + minimatch: 3.1.2 + object.entries: 1.1.6 + object.fromentries: 2.0.6 + object.hasown: 1.1.2 + object.values: 1.1.6 + prop-types: 15.8.1 + resolve: 2.0.0-next.4 + semver: 6.3.0 + string.prototype.matchall: 4.0.8 + dev: false + /eslint-plugin-testing-library/5.11.0_iukboom6ndih5an6iafl45j2fe: resolution: {integrity: sha512-ELY7Gefo+61OfXKlQeXNIDVVLPcvKTeiQOoMZG9TeuWa7Ln4dUNRv8JdRWBQI9Mbb427XGlVB1aa1QPZxBJM8Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} @@ -17824,6 +18321,14 @@ packages: esrecurse: 4.3.0 estraverse: 5.3.0 + /eslint-scope/7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: false + /eslint-utils/2.1.0: resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} engines: {node: '>=6'} @@ -18068,6 +18573,52 @@ packages: transitivePeerDependencies: - supports-color + /eslint/8.49.0: + resolution: {integrity: sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0_eslint@8.49.0 + '@eslint-community/regexpp': 4.8.1 + '@eslint/eslintrc': 2.1.2 + '@eslint/js': 8.49.0 + '@humanwhocodes/config-array': 0.11.11 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.19.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: false + /espree/9.4.1: resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -18085,6 +18636,15 @@ packages: acorn-jsx: 5.3.2_acorn@8.10.0 eslint-visitor-keys: 3.4.2 + /espree/9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.10.0 + acorn-jsx: 5.3.2_acorn@8.10.0 + eslint-visitor-keys: 3.4.3 + dev: false + /esprima/4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -18706,18 +19266,6 @@ packages: debug: optional: true - /follow-redirects/1.15.2_debug@4.3.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dependencies: - debug: 4.3.2 - dev: false - /for-each/0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: @@ -19944,6 +20492,13 @@ packages: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} + /import-as-string/1.0.0: + resolution: {integrity: sha512-+o1QSsS7Z9b5t+9TpnRdQf91OVjcRdQ+WynFZOkvVtxnNvIIzL2DQaLsc+R0jBNB673DLQQ42YjaowOwU9NQ/Q==} + engines: {node: '>=12'} + dependencies: + callsite: 1.0.0 + dev: false + /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -22768,7 +23323,7 @@ packages: /mock-fs/5.2.0: resolution: {integrity: sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==} engines: {node: '>=12.0.0'} - dev: true + dev: false /module-details-from-path/1.0.3: resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} @@ -23167,6 +23722,46 @@ packages: - babel-plugin-macros dev: false + /next/13.4.19_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==} + engines: {node: '>=16.8.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + sass: + optional: true + dependencies: + '@next/env': 13.4.19 + '@swc/helpers': 0.5.1 + busboy: 1.6.0 + caniuse-lite: 1.0.30001532 + postcss: 8.4.14 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + styled-jsx: 5.1.1_react@18.2.0 + watchpack: 2.4.0 + zod: 3.21.4 + optionalDependencies: + '@next/swc-darwin-arm64': 13.4.19 + '@next/swc-darwin-x64': 13.4.19 + '@next/swc-linux-arm64-gnu': 13.4.19 + '@next/swc-linux-arm64-musl': 13.4.19 + '@next/swc-linux-x64-gnu': 13.4.19 + '@next/swc-linux-x64-musl': 13.4.19 + '@next/swc-win32-arm64-msvc': 13.4.19 + '@next/swc-win32-ia32-msvc': 13.4.19 + '@next/swc-win32-x64-msvc': 13.4.19 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /ngrok/5.0.0-beta.2: resolution: {integrity: sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ==} engines: {node: '>=14.2'} @@ -28364,6 +28959,16 @@ packages: tslib: 1.14.1 typescript: 5.1.6 + /tsutils/3.21.0_typescript@5.2.2: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 5.2.2 + dev: false + /tsx/3.12.2: resolution: {integrity: sha512-ykAEkoBg30RXxeOMVeZwar+JH632dZn9EUJVyJwhfag62k6UO/dIyJEV58YuLF6e5BTdV/qmbQrpkWqjq9cUnQ==} hasBin: true @@ -28585,7 +29190,6 @@ packages: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} hasBin: true - dev: true /ufo/1.3.0: resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} From f5c99fc750f4799a0b2fc4d389a51f97179c9159 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sat, 16 Sep 2023 16:56:47 +0100 Subject: [PATCH 24/53] Got the template files working correctly after building --- packages/cli/package.json | 2 +- packages/cli/src/frameworks/nextjs/index.ts | 38 +++++++----------- packages/cli/src/paths.ts | 14 +++++++ .../files => templates/nextjs}/apiRoute.js | 0 .../files => templates/nextjs}/exampleJob.js | 0 .../files => templates/nextjs}/jobsIndex.js | 0 .../files => templates/nextjs}/trigger.js | 0 .../cli/src/utils/createFileFromTemplate.ts | 3 -- .../cli/src/utils/readFileIgnoringMock.ts | 8 ++++ packages/cli/tsup.config.ts | 4 +- pnpm-lock.yaml | 39 +++++++++---------- 11 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 packages/cli/src/paths.ts rename packages/cli/src/{frameworks/nextjs/files => templates/nextjs}/apiRoute.js (100%) rename packages/cli/src/{frameworks/nextjs/files => templates/nextjs}/exampleJob.js (100%) rename packages/cli/src/{frameworks/nextjs/files => templates/nextjs}/jobsIndex.js (100%) rename packages/cli/src/{frameworks/nextjs/files => templates/nextjs}/trigger.js (100%) create mode 100644 packages/cli/src/utils/readFileIgnoringMock.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 5c9fbe3835d..d1080056a98 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,7 +41,6 @@ "@types/mock-fs": "^4.13.1", "@types/node": "16", "@types/node-fetch": "^2.6.2", - "mock-fs": "^5.2.0", "rimraf": "^3.0.2", "tsup": "^6.5.0", "type-fest": "^3.6.0", @@ -67,6 +66,7 @@ "gradient-string": "^2.0.2", "inquirer": "^9.1.4", "localtunnel": "^2.0.2", + "mock-fs": "^5.2.0", "nanoid": "^4.0.2", "ngrok": "5.0.0-beta.2", "node-fetch": "^3.3.0", diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 970c98d2189..7e3f2ee9853 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -1,18 +1,17 @@ +import fs from "fs/promises"; +import pathModule from "path"; import { Framework } from ".."; import { InstallPackage } from "../../utils/addDependencies"; +import { createFileFromTemplate } from "../../utils/createFileFromTemplate"; +import { pathExists } from "../../utils/fileSystem"; import { PackageManager } from "../../utils/getUserPkgManager"; -import pathModule from "path"; -import { readPackageJson } from "../../utils/readPackageJson"; import { logger } from "../../utils/logger"; -import { pathExists } from "../../utils/fileSystem"; -import { parse } from "tsconfck"; -import { detectMiddlewareUsage } from "./middleware"; -import { removeFileExtension } from "../../utils/removeFileExtension"; import { getPathAlias } from "../../utils/pathAlias"; -import { createFileFromTemplate } from "../../utils/createFileFromTemplate"; -import fs from "fs/promises"; -import path from "path"; -import { fileURLToPath } from "url"; +import { readFileIgnoringMock } from "../../utils/readFileIgnoringMock"; +import { readPackageJson } from "../../utils/readPackageJson"; +import { removeFileExtension } from "../../utils/removeFileExtension"; +import { detectMiddlewareUsage } from "./middleware"; +import { rootPath, templatesPath } from "../../paths"; export class NextJs implements Framework { id = "nextjs"; @@ -124,19 +123,14 @@ async function createTriggerPageRoute( isTypescriptProject: boolean, usesSrcDir = false ) { - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); + const templatesDir = pathModule.join(templatesPath(), "nextjs"); - const pathAlias = getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); + const pathAlias = await getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); const fileExtension = isTypescriptProject ? ".ts" : ".js"; //pages/api/trigger.js or src/pages/api/trigger.js const apiRoutePath = pathModule.join(path, "pages", "api", `trigger${fileExtension}`); - - //todo load the file from a file that is copied by tsup - //e.g. templates/nextjs/whatever.js - //load the file using the - + const apiRouteTemplate = await readFileIgnoringMock(pathModule.join(templatesDir, "apiRoute.js")); const apiRouteResult = await createFileFromTemplate({ template: apiRouteTemplate, replacements: { @@ -151,9 +145,7 @@ async function createTriggerPageRoute( //trigger.js or src/trigger.js const triggerFilePath = pathModule.join(path, `trigger${fileExtension}`); - const triggerTemplate = await readFileIgnoringMock( - pathModule.join(__dirname, "files", "trigger.js") - ); + const triggerTemplate = await readFileIgnoringMock(pathModule.join(templatesDir, "trigger.js")); const triggerResult = await createFileFromTemplate({ template: triggerTemplate, replacements: { @@ -172,7 +164,7 @@ async function createTriggerPageRoute( //jobs/examples.js or src/jobs/examples.js const exampleJobFilePath = pathModule.join(exampleDirectory, `examples${fileExtension}`); const exampleJobTemplate = await readFileIgnoringMock( - pathModule.join(__dirname, "files", "exampleJob.js") + pathModule.join(templatesDir, "exampleJob.js") ); const exampleJobResult = await createFileFromTemplate({ template: exampleJobTemplate, @@ -189,7 +181,7 @@ async function createTriggerPageRoute( //jobs/index.js or src/jobs/index.js const jobsIndexFilePath = pathModule.join(exampleDirectory, `index${fileExtension}`); const jobsIndexTemplate = await readFileIgnoringMock( - pathModule.join(__dirname, "files", "jobsIndex.js") + pathModule.join(templatesDir, "jobsIndex.js") ); const jobsIndexResult = await createFileFromTemplate({ template: jobsIndexTemplate, diff --git a/packages/cli/src/paths.ts b/packages/cli/src/paths.ts new file mode 100644 index 00000000000..64dd8c90f00 --- /dev/null +++ b/packages/cli/src/paths.ts @@ -0,0 +1,14 @@ +import path from "path"; +import { fileURLToPath } from "url"; + +export function rootPath() { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + return __dirname; +} + +export function templatesPath() { + const root = rootPath(); + const templatePath = path.join(root, "templates"); + return templatePath; +} diff --git a/packages/cli/src/frameworks/nextjs/files/apiRoute.js b/packages/cli/src/templates/nextjs/apiRoute.js similarity index 100% rename from packages/cli/src/frameworks/nextjs/files/apiRoute.js rename to packages/cli/src/templates/nextjs/apiRoute.js diff --git a/packages/cli/src/frameworks/nextjs/files/exampleJob.js b/packages/cli/src/templates/nextjs/exampleJob.js similarity index 100% rename from packages/cli/src/frameworks/nextjs/files/exampleJob.js rename to packages/cli/src/templates/nextjs/exampleJob.js diff --git a/packages/cli/src/frameworks/nextjs/files/jobsIndex.js b/packages/cli/src/templates/nextjs/jobsIndex.js similarity index 100% rename from packages/cli/src/frameworks/nextjs/files/jobsIndex.js rename to packages/cli/src/templates/nextjs/jobsIndex.js diff --git a/packages/cli/src/frameworks/nextjs/files/trigger.js b/packages/cli/src/templates/nextjs/trigger.js similarity index 100% rename from packages/cli/src/frameworks/nextjs/files/trigger.js rename to packages/cli/src/templates/nextjs/trigger.js diff --git a/packages/cli/src/utils/createFileFromTemplate.ts b/packages/cli/src/utils/createFileFromTemplate.ts index da36cdc5c19..c3b57ba20ae 100644 --- a/packages/cli/src/utils/createFileFromTemplate.ts +++ b/packages/cli/src/utils/createFileFromTemplate.ts @@ -28,12 +28,9 @@ export async function createFileFromTemplate(params: { const output = replaceAll(params.template, params.replacements); const directoryName = path.dirname(params.outputPath); - console.log(`Creating directory ${directoryName}`); await fs.mkdir(directoryName, { recursive: true }); await fs.writeFile(params.outputPath, output); - console.log(`Created file ${params.outputPath}`); - return { success: true, alreadyExisted: false, diff --git a/packages/cli/src/utils/readFileIgnoringMock.ts b/packages/cli/src/utils/readFileIgnoringMock.ts new file mode 100644 index 00000000000..15ab7f688be --- /dev/null +++ b/packages/cli/src/utils/readFileIgnoringMock.ts @@ -0,0 +1,8 @@ +import mock from "mock-fs"; +import fs from "fs/promises"; + +export function readFileIgnoringMock(filePath: string): Promise { + return mock.bypass(async () => { + return await fs.readFile(filePath, { encoding: "utf-8" }); + }); +} diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts index 94937b364e0..40b3cf10f37 100644 --- a/packages/cli/tsup.config.ts +++ b/packages/cli/tsup.config.ts @@ -1,6 +1,8 @@ import { defineConfig } from "tsup"; const isDev = process.env.npm_lifecycle_event === "dev"; +//command to copy the "templates" folder to dist/templates +const copyTemplates = "cp -r src/templates dist"; export default defineConfig({ clean: true, @@ -12,5 +14,5 @@ export default defineConfig({ sourcemap: true, target: "esnext", outDir: "dist", - onSuccess: isDev ? "node dist/index.js" : undefined, + onSuccess: isDev ? `${copyTemplates} && node dist/index.js` : copyTemplates, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d2aeda5fae..f16f37b886a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -351,7 +351,7 @@ importers: devDependencies: eslint: 8.31.0 eslint-config-prettier: 8.6.0_eslint@8.31.0 - eslint-config-turbo: 1.10.13_eslint@8.31.0 + eslint-config-turbo: 1.10.14_eslint@8.31.0 eslint-plugin-react: 7.31.8_eslint@8.31.0 typescript: 4.9.4 @@ -983,7 +983,6 @@ importers: dotenv: ^16.3.1 execa: ^7.0.0 gradient-string: ^2.0.2 - import-as-string: ^1.0.0 inquirer: ^9.1.4 localtunnel: ^2.0.2 mock-fs: ^5.2.0 @@ -1015,7 +1014,6 @@ importers: dotenv: 16.3.1 execa: 7.0.0 gradient-string: 2.0.2 - import-as-string: 1.0.0 inquirer: 9.1.4 localtunnel: 2.0.2 mock-fs: 5.2.0 @@ -14348,7 +14346,7 @@ packages: /axios/0.21.4_debug@4.3.2: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2 + follow-redirects: 1.15.2_debug@4.3.2 transitivePeerDependencies: - debug dev: false @@ -15099,10 +15097,6 @@ packages: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} dev: false - /callsite/1.0.0: - resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} - dev: false - /callsites/3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -17345,13 +17339,13 @@ packages: eslint: 8.31.0 dev: true - /eslint-config-turbo/1.10.13_eslint@8.31.0: - resolution: {integrity: sha512-Ffa0SxkRCPMtfUX/HDanEqsWoLwZTQTAXO9W4IsOtycb2MzJDrVcLmoFW5sMwCrg7gjqbrC4ZJoD+1SPPzIVqg==} + /eslint-config-turbo/1.10.14_eslint@8.31.0: + resolution: {integrity: sha512-ZeB+IcuFXy1OICkLuAplVa0euoYbhK+bMEQd0nH9+Lns18lgZRm33mVz/iSoH9VdUzl/1ZmFmoK+RpZc+8R80A==} peerDependencies: eslint: '>6.6.0' dependencies: eslint: 8.31.0 - eslint-plugin-turbo: 1.10.13_eslint@8.31.0 + eslint-plugin-turbo: 1.10.14_eslint@8.31.0 dev: true /eslint-doc-generator/1.4.3_eslint@8.45.0: @@ -18289,8 +18283,8 @@ packages: - typescript dev: true - /eslint-plugin-turbo/1.10.13_eslint@8.31.0: - resolution: {integrity: sha512-el4AAmn0zXmvHEyp1h0IQMfse10Vy8g5Vbg4IU3+vD9CSj5sDbX07iFVt8sCKg7og9Q5FAa9mXzlCf7t4vYgzg==} + /eslint-plugin-turbo/1.10.14_eslint@8.31.0: + resolution: {integrity: sha512-sBdBDnYr9AjT1g4lR3PBkZDonTrMnR4TvuGv5W0OiF7z9az1rI68yj2UHJZvjkwwcGu5mazWA1AfB0oaagpmfg==} peerDependencies: eslint: '>6.6.0' dependencies: @@ -19266,6 +19260,18 @@ packages: debug: optional: true + /follow-redirects/1.15.2_debug@4.3.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dependencies: + debug: 4.3.2 + dev: false + /for-each/0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: @@ -20492,13 +20498,6 @@ packages: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} - /import-as-string/1.0.0: - resolution: {integrity: sha512-+o1QSsS7Z9b5t+9TpnRdQf91OVjcRdQ+WynFZOkvVtxnNvIIzL2DQaLsc+R0jBNB673DLQQ42YjaowOwU9NQ/Q==} - engines: {node: '>=12'} - dependencies: - callsite: 1.0.0 - dev: false - /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} From 2c1be8e163d0f2e0d8c1307da09f95fc6886b32b Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sat, 16 Sep 2023 17:01:24 +0100 Subject: [PATCH 25/53] Next steps are now framework specific --- packages/cli/src/commands/init.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index db3208656da..e3815c0c958 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -6,7 +6,7 @@ import pathModule from "path"; import { simpleGit } from "simple-git"; import { promptApiKey, promptTriggerUrl } from "../cli/index"; import { CLOUD_API_URL, CLOUD_TRIGGER_URL, COMMAND_NAME } from "../consts"; -import { frameworkNames, getFramework } from "../frameworks"; +import { Framework, frameworkNames, getFramework } from "../frameworks"; import { telemetryClient } from "../telemetry/telemetry"; import { addDependencies } from "../utils/addDependencies"; import { @@ -15,7 +15,7 @@ import { setApiUrlEnvironmentVariable, } from "../utils/env"; import { readJSONFile } from "../utils/fileSystem"; -import { getUserPackageManager } from "../utils/getUserPkgManager"; +import { PackageManager, getUserPackageManager } from "../utils/getUserPkgManager"; import { logger } from "../utils/logger"; import { resolvePath } from "../utils/parseNameAndPath"; import { readPackageJson } from "../utils/readPackageJson"; @@ -129,17 +129,22 @@ export const initCommand = async (options: InitCommandOptions) => { await addConfigurationToPackageJson(resolvedPath, resolvedOptions); - await printNextSteps(resolvedOptions, authorizedKey); + await printNextSteps(resolvedOptions, authorizedKey, packageManager, framework); telemetryClient.init.completed(resolvedOptions); }; -async function printNextSteps(options: ResolvedOptions, authorizedKey: WhoamiResponse) { +async function printNextSteps( + options: ResolvedOptions, + authorizedKey: WhoamiResponse, + packageManager: PackageManager, + framework: Framework +) { const projectUrl = `${options.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`; logger.success(`βœ… Successfully initialized Trigger.dev!`); logger.info("Next steps:"); - logger.info(` 1. Run your Next.js project locally with 'npm run dev'`); + logger.info(` 1. Run your ${framework.name} project locally with '${packageManager} run dev'`); logger.info( ` 2. In a separate terminal, run 'npx @trigger.dev/cli@latest dev' to watch for changes and automatically register Trigger.dev jobs` ); From a47eadb7482131f5ad84a755306d7fba2ff207d6 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sat, 16 Sep 2023 17:07:01 +0100 Subject: [PATCH 26/53] createFileFromTemplate now works with a path again. Uses mock if specified. --- packages/cli/src/frameworks/nextjs/index.ts | 16 ++++------------ .../cli/src/utils/createFileFromTemplate.test.ts | 5 +++-- packages/cli/src/utils/createFileFromTemplate.ts | 13 +++++++++++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 7e3f2ee9853..e3e79fa16f7 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -130,9 +130,8 @@ async function createTriggerPageRoute( //pages/api/trigger.js or src/pages/api/trigger.js const apiRoutePath = pathModule.join(path, "pages", "api", `trigger${fileExtension}`); - const apiRouteTemplate = await readFileIgnoringMock(pathModule.join(templatesDir, "apiRoute.js")); const apiRouteResult = await createFileFromTemplate({ - template: apiRouteTemplate, + templatePath: pathModule.join(templatesDir, "apiRoute.js"), replacements: { routePathPrefix: pathAlias ? pathAlias + "/" : "../../", }, @@ -145,9 +144,8 @@ async function createTriggerPageRoute( //trigger.js or src/trigger.js const triggerFilePath = pathModule.join(path, `trigger${fileExtension}`); - const triggerTemplate = await readFileIgnoringMock(pathModule.join(templatesDir, "trigger.js")); const triggerResult = await createFileFromTemplate({ - template: triggerTemplate, + templatePath: pathModule.join(templatesDir, "trigger.js"), replacements: { endpointSlug, }, @@ -163,11 +161,8 @@ async function createTriggerPageRoute( //jobs/examples.js or src/jobs/examples.js const exampleJobFilePath = pathModule.join(exampleDirectory, `examples${fileExtension}`); - const exampleJobTemplate = await readFileIgnoringMock( - pathModule.join(templatesDir, "exampleJob.js") - ); const exampleJobResult = await createFileFromTemplate({ - template: exampleJobTemplate, + templatePath: pathModule.join(templatesDir, "exampleJob.js"), replacements: { jobsPathPrefix: pathAlias ? pathAlias + "/" : "../", }, @@ -180,11 +175,8 @@ async function createTriggerPageRoute( //jobs/index.js or src/jobs/index.js const jobsIndexFilePath = pathModule.join(exampleDirectory, `index${fileExtension}`); - const jobsIndexTemplate = await readFileIgnoringMock( - pathModule.join(templatesDir, "jobsIndex.js") - ); const jobsIndexResult = await createFileFromTemplate({ - template: jobsIndexTemplate, + templatePath: pathModule.join(templatesDir, "jobsIndex.js"), replacements: { jobsPathPrefix: pathAlias ? pathAlias + "/" : "../", }, diff --git a/packages/cli/src/utils/createFileFromTemplate.test.ts b/packages/cli/src/utils/createFileFromTemplate.test.ts index b9468634a77..464b1f3b13f 100644 --- a/packages/cli/src/utils/createFileFromTemplate.test.ts +++ b/packages/cli/src/utils/createFileFromTemplate.test.ts @@ -1,5 +1,6 @@ import fs from "fs/promises"; import mock from "mock-fs"; +import path from "path"; import { createFileFromTemplate, replaceAll } from "./createFileFromTemplate"; afterEach(() => { @@ -38,14 +39,14 @@ describe("Template files", () => { }, }); - const template = await fs.readFile("templates/some-file.js", "utf-8"); const result = await createFileFromTemplate({ - template, + templatePath: path.join("templates", "some-file.js"), replacements: { routePathPrefix: "@/", anotherPathPrefix: "@/src/", }, outputPath: "foo/output.ts", + mockTemplatePath: true, }); expect(result.success).toEqual(true); diff --git a/packages/cli/src/utils/createFileFromTemplate.ts b/packages/cli/src/utils/createFileFromTemplate.ts index c3b57ba20ae..6a106456c97 100644 --- a/packages/cli/src/utils/createFileFromTemplate.ts +++ b/packages/cli/src/utils/createFileFromTemplate.ts @@ -1,6 +1,7 @@ import fs from "fs/promises"; import { pathExists } from "./fileSystem"; import path from "path"; +import { readFileIgnoringMock } from "./readFileIgnoringMock"; type Result = | { @@ -13,10 +14,18 @@ type Result = }; export async function createFileFromTemplate(params: { - template: string; + templatePath: string; replacements: Record; outputPath: string; + mockTemplatePath?: boolean; }): Promise { + let template = ""; + if (params.mockTemplatePath === true) { + template = await fs.readFile(params.templatePath, "utf-8"); + } else { + template = await readFileIgnoringMock(params.templatePath); + } + if (await pathExists(params.outputPath)) { return { success: true, @@ -25,7 +34,7 @@ export async function createFileFromTemplate(params: { } try { - const output = replaceAll(params.template, params.replacements); + const output = replaceAll(template, params.replacements); const directoryName = path.dirname(params.outputPath); await fs.mkdir(directoryName, { recursive: true }); From 113ef99d9922dc8486fe0a59f9adb31119c076b1 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sat, 16 Sep 2023 17:10:17 +0100 Subject: [PATCH 27/53] Renamed apiRoute.js to pagesApiRoute.js --- packages/cli/src/frameworks/nextjs/index.ts | 5 ++--- .../src/templates/nextjs/{apiRoute.js => pagesApiRoute.js} | 0 2 files changed, 2 insertions(+), 3 deletions(-) rename packages/cli/src/templates/nextjs/{apiRoute.js => pagesApiRoute.js} (100%) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index e3e79fa16f7..cfc9cd20147 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -1,17 +1,16 @@ import fs from "fs/promises"; import pathModule from "path"; import { Framework } from ".."; +import { templatesPath } from "../../paths"; import { InstallPackage } from "../../utils/addDependencies"; import { createFileFromTemplate } from "../../utils/createFileFromTemplate"; import { pathExists } from "../../utils/fileSystem"; import { PackageManager } from "../../utils/getUserPkgManager"; import { logger } from "../../utils/logger"; import { getPathAlias } from "../../utils/pathAlias"; -import { readFileIgnoringMock } from "../../utils/readFileIgnoringMock"; import { readPackageJson } from "../../utils/readPackageJson"; import { removeFileExtension } from "../../utils/removeFileExtension"; import { detectMiddlewareUsage } from "./middleware"; -import { rootPath, templatesPath } from "../../paths"; export class NextJs implements Framework { id = "nextjs"; @@ -131,7 +130,7 @@ async function createTriggerPageRoute( //pages/api/trigger.js or src/pages/api/trigger.js const apiRoutePath = pathModule.join(path, "pages", "api", `trigger${fileExtension}`); const apiRouteResult = await createFileFromTemplate({ - templatePath: pathModule.join(templatesDir, "apiRoute.js"), + templatePath: pathModule.join(templatesDir, "pagesApiRoute.js"), replacements: { routePathPrefix: pathAlias ? pathAlias + "/" : "../../", }, diff --git a/packages/cli/src/templates/nextjs/apiRoute.js b/packages/cli/src/templates/nextjs/pagesApiRoute.js similarity index 100% rename from packages/cli/src/templates/nextjs/apiRoute.js rename to packages/cli/src/templates/nextjs/pagesApiRoute.js From 4aafb19ef79d770542fd54fa443fc5d95c775010 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sat, 16 Sep 2023 17:15:08 +0100 Subject: [PATCH 28/53] Simplified pages file generation --- packages/cli/src/frameworks/nextjs/index.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index cfc9cd20147..4abe32e6ec2 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -48,17 +48,15 @@ export class NextJs implements Framework { } const nextJsDir = await detectPagesOrAppDir(path); - const routeDir = pathModule.join(path, usesSrcDir ? "src" : ""); + const pathAlias = await getPathAlias({ + projectPath: path, + isTypescriptProject: options.typescript, + usesSrcDir, + }); if (nextJsDir === "pages") { - await createTriggerPageRoute( - path, - routeDir, - options.endpointSlug, - options.typescript, - usesSrcDir - ); + await createTriggerPageRoute(routeDir, options.endpointSlug, options.typescript, pathAlias); } else { await createTriggerAppRoute( path, @@ -116,15 +114,12 @@ export async function detectPagesOrAppDir(path: string): Promise<"pages" | "app" } async function createTriggerPageRoute( - projectPath: string, path: string, endpointSlug: string, isTypescriptProject: boolean, - usesSrcDir = false + pathAlias: string | undefined ) { const templatesDir = pathModule.join(templatesPath(), "nextjs"); - - const pathAlias = await getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); const fileExtension = isTypescriptProject ? ".ts" : ".js"; //pages/api/trigger.js or src/pages/api/trigger.js From 8e10f66759f2c3d0f346b36d7956f567b1cbb500 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sat, 16 Sep 2023 17:23:00 +0100 Subject: [PATCH 29/53] Next.js app API route template --- packages/cli/src/templates/nextjs/appApiRoute.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/cli/src/templates/nextjs/appApiRoute.js diff --git a/packages/cli/src/templates/nextjs/appApiRoute.js b/packages/cli/src/templates/nextjs/appApiRoute.js new file mode 100644 index 00000000000..546336103f1 --- /dev/null +++ b/packages/cli/src/templates/nextjs/appApiRoute.js @@ -0,0 +1,7 @@ +import { createAppRoute } from "@trigger.dev/nextjs"; +import { client } from "${routePathPrefix}trigger"; + +import "${routePathPrefix}jobs"; + +//this route is used to send and receive data with Trigger.dev +export const { POST, dynamic } = createAppRoute(client); From 8ddf9118930f2aa38c607adef3e4c796f335cd9c Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sat, 16 Sep 2023 17:23:15 +0100 Subject: [PATCH 30/53] Next.js App routing support, with common files logic shared --- packages/cli/src/frameworks/nextjs/index.ts | 164 +++++--------------- 1 file changed, 37 insertions(+), 127 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 4abe32e6ec2..45fed0db429 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -58,13 +58,7 @@ export class NextJs implements Framework { if (nextJsDir === "pages") { await createTriggerPageRoute(routeDir, options.endpointSlug, options.typescript, pathAlias); } else { - await createTriggerAppRoute( - path, - routeDir, - options.endpointSlug, - options.typescript, - usesSrcDir - ); + await createTriggerAppRoute(routeDir, options.endpointSlug, options.typescript, pathAlias); } } @@ -136,6 +130,42 @@ async function createTriggerPageRoute( } logger.success(`βœ… Created API route at ${apiRoutePath}`); + await createJobsAndTriggerFile(path, endpointSlug, fileExtension, pathAlias, templatesDir); +} + +async function createTriggerAppRoute( + path: string, + endpointSlug: string, + isTypescriptProject: boolean, + pathAlias: string | undefined +) { + const templatesDir = pathModule.join(templatesPath(), "nextjs"); + const fileExtension = isTypescriptProject ? ".ts" : ".js"; + + //app/api/trigger/route.js or src/app/api/trigger/route.js + const apiRoutePath = pathModule.join(path, "app", "api", "trigger", `route${fileExtension}`); + const apiRouteResult = await createFileFromTemplate({ + templatePath: pathModule.join(templatesDir, "appApiRoute.js"), + replacements: { + routePathPrefix: pathAlias ? pathAlias + "/" : "../../", + }, + outputPath: apiRoutePath, + }); + if (!apiRouteResult.success) { + throw new Error("Failed to create API route file"); + } + logger.success(`βœ… Created API route at ${apiRoutePath}`); + + await createJobsAndTriggerFile(path, endpointSlug, fileExtension, pathAlias, templatesDir); +} + +async function createJobsAndTriggerFile( + path: string, + endpointSlug: string, + fileExtension: string, + pathAlias: string | undefined, + templatesDir: string +) { //trigger.js or src/trigger.js const triggerFilePath = pathModule.join(path, `trigger${fileExtension}`); const triggerResult = await createFileFromTemplate({ @@ -181,123 +211,3 @@ async function createTriggerPageRoute( } logger.success(`βœ… Created jobs index at ${jobsIndexFilePath}`); } - -async function createTriggerAppRoute( - projectPath: string, - path: string, - endpointSlug: string, - isTypescriptProject: boolean, - usesSrcDir = false -) { - const pathAlias = getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }); - - const extension = isTypescriptProject ? ".ts" : ".js"; - const triggerFileName = `trigger${extension}`; - const examplesFileName = `examples${extension}`; - const examplesIndexFileName = `index${extension}`; - const routeFileName = `route${extension}`; - - const routePathPrefix = pathAlias ? pathAlias + "/" : "../../../"; - - const routeContent = ` -import { createAppRoute } from "@trigger.dev/nextjs"; -import { client } from "${routePathPrefix}trigger"; - - -import "${routePathPrefix}jobs"; - -//this route is used to send and receive data with Trigger.dev -export const { POST, dynamic } = createAppRoute(client); -`; - - const triggerContent = ` -import { TriggerClient } from "@trigger.dev/sdk"; - -export const client = new TriggerClient({ - id: "${endpointSlug}", - apiKey: process.env.TRIGGER_API_KEY, - apiUrl: process.env.TRIGGER_API_URL, -}); - `; - - const jobsPathPrefix = pathAlias ? pathAlias + "/" : "../"; - - const jobsContent = ` -import { eventTrigger } from "@trigger.dev/sdk"; -import { client } from "${jobsPathPrefix}trigger"; - -// Your first job -// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline -client.defineJob({ - // This is the unique identifier for your Job, it must be unique across all Jobs in your project - id: "example-job", - name: "Example Job: a joke with a delay", - version: "0.0.1", - // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction - trigger: eventTrigger({ - name: "example.event", - }), - run: async (payload, io, ctx) => { - // This logs a message to the console - await io.logger.info("πŸ§ͺ Example Job: a joke with a delay"); - await io.logger.info("How do you comfort a JavaScript bug?"); - // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year - await io.wait("Wait 5 seconds for the punchline...", 5); - await io.logger.info("You console it! 🀦"); - await io.logger.info( - "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" - ); - // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job - }, -}); - -`; - - const examplesIndexContent = ` -// import all your job files here - -export * from "./examples" -`; - - const directories = pathModule.join(path, "app", "api", "trigger"); - await fs.mkdir(directories, { recursive: true }); - - const fileExists = await pathExists(pathModule.join(directories, routeFileName)); - - if (fileExists) { - logger.info("Skipping creation of app route because it already exists"); - return; - } - - await fs.writeFile(pathModule.join(directories, routeFileName), routeContent); - - logger.success( - `βœ… Created app route at ${usesSrcDir ? "src/" : ""}app/api/${removeFileExtension( - triggerFileName - )}/${routeFileName}` - ); - - const triggerFileExists = await pathExists(pathModule.join(path, triggerFileName)); - - if (!triggerFileExists) { - await fs.writeFile(pathModule.join(path, triggerFileName), triggerContent); - - logger.success(`βœ… Created trigger client at ${usesSrcDir ? "src/" : ""}${triggerFileName}`); - } - - const exampleDirectories = pathModule.join(path, "jobs"); - await fs.mkdir(exampleDirectories, { recursive: true }); - - const exampleFileExists = await pathExists(pathModule.join(exampleDirectories, examplesFileName)); - - if (!exampleFileExists) { - await fs.writeFile(pathModule.join(exampleDirectories, examplesFileName), jobsContent); - - await fs.writeFile( - pathModule.join(exampleDirectories, examplesIndexFileName), - examplesIndexContent - ); - - logger.success(`βœ… Created example job at ${usesSrcDir ? "src/" : ""}jobs/examples.ts`); - } -} From 451eceabe6307036a93d2e90fdd2ca89b8d8f640 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sun, 17 Sep 2023 18:41:59 +0100 Subject: [PATCH 31/53] =?UTF-8?q?Dev=20command=20now=20uses=20framework=20?= =?UTF-8?q?default=20values=20if=20they=20exist=20and=20aren=E2=80=99t=20o?= =?UTF-8?q?verridden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/cli/index.ts | 6 +- packages/cli/src/commands/dev.ts | 104 +++++++++++++------- packages/cli/src/frameworks/index.ts | 1 + packages/cli/src/frameworks/nextjs/index.ts | 1 + 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/packages/cli/src/cli/index.ts b/packages/cli/src/cli/index.ts index 66477ac1463..8ce33ae2421 100644 --- a/packages/cli/src/cli/index.ts +++ b/packages/cli/src/cli/index.ts @@ -43,9 +43,9 @@ program .command("dev") .description("Tunnel your local Next.js project to Trigger.dev and start running jobs") .argument("[path]", "The path to the project", ".") - .option("-p, --port ", "The local port your server is on", "3000") - .option("-H, --hostname ", "Hostname on which the application is served", "localhost") - .option("-e, --env-file ", "The name of the env file to load", ".env.local") + .option("-p, --port ", "Override the local port your server is on") + .option("-H, --hostname ", "Override the hostname on which the application is served") + .option("-e, --env-file ", "Override the name of the env file to load") .option( "-i, --client-id ", "The ID of the client to use for this project. Will use the value from the package.json file if not provided." diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 5a88680dc4c..b845ab7c0fd 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -12,20 +12,24 @@ import { getTriggerApiDetails } from "../utils/getTriggerApiDetails"; import { logger } from "../utils/logger"; import { resolvePath } from "../utils/parseNameAndPath"; import { TriggerApi } from "../utils/triggerApi"; -import { run as ncuRun } from 'npm-check-updates' +import { run as ncuRun } from "npm-check-updates"; import chalk from "chalk"; +import { getUserPackageManager } from "../utils/getUserPkgManager"; +import { Framework, getFramework } from "../frameworks"; +import { getEnvFilename } from "../utils/env"; const asyncExecFile = util.promisify(childProcess.execFile); export const DevCommandOptionsSchema = z.object({ - port: z.coerce.number(), - hostname: z.string(), - envFile: z.string(), + port: z.coerce.number().optional(), + hostname: z.string().optional(), + envFile: z.string().optional(), handlerPath: z.string(), clientId: z.string().optional(), }); export type DevCommandOptions = z.infer; +type ResolvedOptions = Omit, "clientId"> & { clientId?: string }; const throttleTimeMs = 1000; @@ -47,7 +51,7 @@ export async function devCommand(path: string, anyOptions: any) { const options = result.data; const resolvedPath = resolvePath(path); - await checkForOutdatedPackages(resolvedPath) + await checkForOutdatedPackages(resolvedPath); // Read from package.json to get the endpointId const endpointId = await getEndpointIdFromPackageJson(resolvedPath, options); @@ -60,11 +64,16 @@ export async function devCommand(path: string, anyOptions: any) { } logger.success(`βœ”οΈ [trigger.dev] Detected TriggerClient id: ${endpointId}`); + //resolve the options using the detected framework (use default if there isn't a matching framework) + const packageManager = await getUserPackageManager(resolvedPath); + const framework = await getFramework(resolvedPath, packageManager); + const resolvedOptions = await resolveOptions(framework, resolvedPath, options); + // Read from .env.local or .env to get the TRIGGER_API_KEY and TRIGGER_API_URL - const apiDetails = await getTriggerApiDetails(resolvedPath, options.envFile); + const apiDetails = await getTriggerApiDetails(resolvedPath, resolvedOptions.envFile); if (!apiDetails) { - telemetryClient.dev.failed("missing_api_key", options); + telemetryClient.dev.failed("missing_api_key", resolvedOptions); return; } @@ -72,9 +81,9 @@ export async function devCommand(path: string, anyOptions: any) { logger.success(`βœ”οΈ [trigger.dev] Found API Key in ${envFile} file`); - logger.info(` [trigger.dev] Looking for Next.js site on port ${options.port}`); + logger.info(` [trigger.dev] Looking for Next.js site on port ${resolvedOptions.port}`); - const localEndpointHandlerUrl = `http://${options.hostname}:${options.port}${options.handlerPath}`; + const localEndpointHandlerUrl = `http://${resolvedOptions.hostname}:${resolvedOptions.port}${resolvedOptions.handlerPath}`; try { await fetch(localEndpointHandlerUrl, { @@ -87,23 +96,27 @@ export async function devCommand(path: string, anyOptions: any) { }); } catch (err) { logger.error( - `❌ [trigger.dev] No server found on port ${options.port}. Make sure your Next.js app is running and try again.` + `❌ [trigger.dev] No server found on port ${resolvedOptions.port}. Make sure your Next.js app is running and try again.` ); - telemetryClient.dev.failed("no_server_found", options); + telemetryClient.dev.failed("no_server_found", resolvedOptions); return; } - telemetryClient.dev.serverRunning(path, options); + telemetryClient.dev.serverRunning(path, resolvedOptions); // Setup tunnel - const endpointUrl = await resolveEndpointUrl(apiUrl, options.port, options.hostname); + const endpointUrl = await resolveEndpointUrl( + apiUrl, + resolvedOptions.port, + resolvedOptions.hostname + ); if (!endpointUrl) { - telemetryClient.dev.failed("failed_to_create_tunnel", options); + telemetryClient.dev.failed("failed_to_create_tunnel", resolvedOptions); return; } - const endpointHandlerUrl = `${endpointUrl}${options.handlerPath}`; - telemetryClient.dev.tunnelRunning(path, options); + const endpointHandlerUrl = `${endpointUrl}${resolvedOptions.handlerPath}`; + telemetryClient.dev.tunnelRunning(path, resolvedOptions); const connectingSpinner = ora(`[trigger.dev] Registering endpoint ${endpointHandlerUrl}...`); @@ -113,7 +126,7 @@ export async function devCommand(path: string, anyOptions: any) { const refresh = async () => { connectingSpinner.start(); - const refreshedEndpointId = await getEndpointIdFromPackageJson(resolvedPath, options); + const refreshedEndpointId = await getEndpointIdFromPackageJson(resolvedPath, resolvedOptions); // Read from .env.local to get the TRIGGER_API_KEY and TRIGGER_API_URL const apiDetails = await getTriggerApiDetails(resolvedPath, envFile); @@ -134,7 +147,7 @@ export async function devCommand(path: string, anyOptions: any) { `πŸ›‘ 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); + telemetryClient.dev.failed("invalid_api_key", resolvedOptions); return; } @@ -159,7 +172,7 @@ export async function devCommand(path: string, anyOptions: any) { if (!hasConnected) { hasConnected = true; - telemetryClient.dev.connected(path, options); + telemetryClient.dev.connected(path, resolvedOptions); } } else { attemptCount++; @@ -170,7 +183,7 @@ export async function devCommand(path: string, anyOptions: any) { attemptCount = 0; if (!hasConnected) { - telemetryClient.dev.failed("failed_to_connect", options); + telemetryClient.dev.failed("failed_to_connect", resolvedOptions); } return; } @@ -208,34 +221,57 @@ export async function devCommand(path: string, anyOptions: any) { throttle(refresh, throttleTimeMs); } -export async function checkForOutdatedPackages(path: string) { +export async function resolveOptions( + framework: Framework | undefined, + path: string, + unresolvedOptions: DevCommandOptions +): Promise { + if (!framework) { + logger.info("Failed to detect framework, using default values"); + return { + port: unresolvedOptions.port ?? 3000, + hostname: unresolvedOptions.hostname ?? "localhost", + envFile: unresolvedOptions.envFile ?? ".env", + handlerPath: unresolvedOptions.handlerPath, + clientId: unresolvedOptions.clientId, + }; + } + + //get env filename + const envName = await getEnvFilename(path, framework.possibleEnvFilenames()); + framework.defaultHostname; + + return { + port: unresolvedOptions.port ?? framework.defaultPort ?? 3000, + hostname: unresolvedOptions.hostname ?? framework.defaultHostname ?? "localhost", + envFile: unresolvedOptions.envFile ?? envName ?? ".env", + handlerPath: unresolvedOptions.handlerPath, + clientId: unresolvedOptions.clientId, + }; +} - const updates = await ncuRun({ +export async function checkForOutdatedPackages(path: string) { + const updates = (await ncuRun({ packageFile: `${path}/package.json`, - filter: "/trigger.dev\/.+$/", + filter: "/trigger.dev/.+$/", upgrade: false, - }) as { + })) as { [key: string]: string; - } + }; - if (typeof updates === 'undefined' || Object.keys(updates).length === 0) { + if (typeof updates === "undefined" || Object.keys(updates).length === 0) { return; } const packageFile = await fs.readFile(`${path}/package.json`); - const data = JSON.parse(Buffer.from(packageFile).toString('utf8')); + const data = JSON.parse(Buffer.from(packageFile).toString("utf8")); const dependencies = data.dependencies; - console.log( - chalk.bgYellow('Updates available for trigger.dev packages') - ); - console.log( - chalk.bgBlue('Run npx @trigger.dev/cli@latest update') - ); + console.log(chalk.bgYellow("Updates available for trigger.dev packages")); + console.log(chalk.bgBlue("Run npx @trigger.dev/cli@latest update")); for (let dep in updates) { console.log(`${dep} ${dependencies[dep]} β†’ ${updates[dep]}`); } - } export async function getEndpointIdFromPackageJson(path: string, options: DevCommandOptions) { diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts index 5d655ed8d02..ecb7c191475 100644 --- a/packages/cli/src/frameworks/index.ts +++ b/packages/cli/src/frameworks/index.ts @@ -12,6 +12,7 @@ export interface Framework { id: string; name: string; defaultHostname: string; + defaultPort: number; isMatch(path: string, packageManager: PackageManager): Promise; dependencies(): Promise; possibleEnvFilenames(): string[]; diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 45fed0db429..7db02810936 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -16,6 +16,7 @@ export class NextJs implements Framework { id = "nextjs"; name = "Next.js"; defaultHostname = "localhost"; + defaultPort = 3000; async isMatch(path: string, packageManager: PackageManager): Promise { const hasNextConfigFile = await detectNextConfigFile(path); From 690129031b01c720bdd21ccab47cde3880eb73ae Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 08:53:57 +0100 Subject: [PATCH 32/53] Unused import --- packages/cli/src/frameworks/nextjs/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 7db02810936..77e837f2959 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -9,7 +9,6 @@ import { PackageManager } from "../../utils/getUserPkgManager"; import { logger } from "../../utils/logger"; import { getPathAlias } from "../../utils/pathAlias"; import { readPackageJson } from "../../utils/readPackageJson"; -import { removeFileExtension } from "../../utils/removeFileExtension"; import { detectMiddlewareUsage } from "./middleware"; export class NextJs implements Framework { From 9fc89453352318e2e2280aa79f23e915f74f429f Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 11:58:18 +0100 Subject: [PATCH 33/53] pathAlias now works for all frameworks --- packages/cli/src/frameworks/nextjs/index.ts | 2 +- packages/cli/src/utils/pathAlias.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 77e837f2959..10d586267c6 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -52,7 +52,7 @@ export class NextJs implements Framework { const pathAlias = await getPathAlias({ projectPath: path, isTypescriptProject: options.typescript, - usesSrcDir, + extraDirectories: usesSrcDir ? ["src"] : undefined, }); if (nextJsDir === "pages") { diff --git a/packages/cli/src/utils/pathAlias.ts b/packages/cli/src/utils/pathAlias.ts index e6b67d9b96f..dc949bb727c 100644 --- a/packages/cli/src/utils/pathAlias.ts +++ b/packages/cli/src/utils/pathAlias.ts @@ -2,7 +2,7 @@ import pathModule from "path"; import { pathExists } from "./fileSystem"; import { parse } from "tsconfck"; -type Options = { projectPath: string; isTypescriptProject: boolean; usesSrcDir: boolean }; +type Options = { projectPath: string; isTypescriptProject: boolean; extraDirectories?: string[] }; // Find the alias that points to the "src" directory. // So for example, the paths object could be: @@ -10,7 +10,11 @@ type Options = { projectPath: string; isTypescriptProject: boolean; usesSrcDir: // "@/*": ["./src/*"] // } // In this case, we would return "@" -export async function getPathAlias({ projectPath, isTypescriptProject, usesSrcDir }: Options) { +export async function getPathAlias({ + projectPath, + isTypescriptProject, + extraDirectories, +}: Options) { const configFileName = isTypescriptProject ? "tsconfig.json" : "jsconfig.json"; const tsConfigPath = pathModule.join(projectPath, configFileName); const configFileExists = await pathExists(tsConfigPath); @@ -35,8 +39,8 @@ export async function getPathAlias({ projectPath, isTypescriptProject, usesSrcDi } const path = value[0]; - if (usesSrcDir) { - return path === "./src/*"; + if (extraDirectories && extraDirectories.length > 0) { + return path === `./${extraDirectories.join("/")}/*`; } else { return path === "./*"; } From e2e5643659ea8fae4a73b01cec8427bb8707b180 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 11:58:32 +0100 Subject: [PATCH 34/53] Added a test to detect Next from the next.config.js --- packages/cli/src/frameworks/nextjs/nextjs.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/frameworks/nextjs/nextjs.test.ts b/packages/cli/src/frameworks/nextjs/nextjs.test.ts index 3a48c409dbc..63f192eb7d2 100644 --- a/packages/cli/src/frameworks/nextjs/nextjs.test.ts +++ b/packages/cli/src/frameworks/nextjs/nextjs.test.ts @@ -17,7 +17,17 @@ describe("Next project detection", () => { expect(framework?.id).toEqual("nextjs"); }); - test("no dependency", async () => { + test("no dependency, has next.config.js", async () => { + mock({ + "package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }), + "next.config.js": "module.exports = {}", + }); + + const framework = await getFramework("", "npm"); + expect(framework?.id).toEqual("nextjs"); + }); + + test("no dependency, no next.config.js", async () => { mock({ "package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }), }); From b9fe053a2aacd5d001f2ff9e3b649cbd33d66231 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 11:58:45 +0100 Subject: [PATCH 35/53] WIP on Remix framework support --- packages/cli/src/frameworks/index.ts | 3 +- packages/cli/src/frameworks/remix/index.ts | 106 ++++++++++++++++++ .../cli/src/frameworks/remix/remix.test.ts | 106 ++++++++++++++++++ packages/cli/src/templates/remix/apiRoute.js | 8 ++ .../cli/src/templates/remix/exampleJob.js | 27 +++++ packages/cli/src/templates/remix/trigger.js | 7 ++ 6 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 packages/cli/src/frameworks/remix/index.ts create mode 100644 packages/cli/src/frameworks/remix/remix.test.ts create mode 100644 packages/cli/src/templates/remix/apiRoute.js create mode 100644 packages/cli/src/templates/remix/exampleJob.js create mode 100644 packages/cli/src/templates/remix/trigger.js diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts index ecb7c191475..06298207375 100644 --- a/packages/cli/src/frameworks/index.ts +++ b/packages/cli/src/frameworks/index.ts @@ -1,6 +1,7 @@ import { InstallPackage } from "../utils/addDependencies"; import { PackageManager } from "../utils/getUserPkgManager"; import { NextJs } from "./nextjs"; +import { Remix } from "./remix"; export type ProjectInstallOptions = { typescript: boolean; @@ -20,7 +21,7 @@ export interface Framework { postInstall(path: string, options: ProjectInstallOptions): Promise; } -const frameworks: Framework[] = [new NextJs()]; +const frameworks: Framework[] = [new NextJs(), new Remix()]; export const getFramework = async ( path: string, diff --git a/packages/cli/src/frameworks/remix/index.ts b/packages/cli/src/frameworks/remix/index.ts new file mode 100644 index 00000000000..55497fbb2ad --- /dev/null +++ b/packages/cli/src/frameworks/remix/index.ts @@ -0,0 +1,106 @@ +import { Framework, ProjectInstallOptions } from ".."; +import { InstallPackage } from "../../utils/addDependencies"; +import { pathExists } from "../../utils/fileSystem"; +import { PackageManager } from "../../utils/getUserPkgManager"; +import pathModule from "path"; +import { getPathAlias } from "../../utils/pathAlias"; +import { createFileFromTemplate } from "../../utils/createFileFromTemplate"; +import { templatesPath } from "../../paths"; +import { logger } from "../../utils/logger"; +import { readPackageJson } from "../../utils/readPackageJson"; + +export class Remix implements Framework { + id = "remix"; + name = "Remix"; + defaultHostname = "localhost"; + defaultPort = 3000; + + async isMatch(path: string, packageManager: PackageManager): Promise { + //check for remix.config.js + const hasConfigFile = await pathExists(pathModule.join(path, "remix.config.js")); + if (hasConfigFile) { + return true; + } + + //check for any packages starting with @remix-run + const packageJsonContent = await readPackageJson(path); + if (!packageJsonContent) { + return false; + } + + const keys = Object.keys(packageJsonContent.dependencies || {}); + const dependencyWithRemix = keys.find((key) => key.startsWith("@remix-run")); + if (dependencyWithRemix) { + return true; + } + + return false; + } + + async dependencies(): Promise { + return [ + { name: "@trigger.dev/sdk", tag: "latest" }, + { name: "@trigger.dev/remix", tag: "latest" }, + { name: "@trigger.dev/react", tag: "latest" }, + ]; + } + + possibleEnvFilenames(): string[] { + return [".env"]; + } + + async install(path: string, { typescript, endpointSlug }: ProjectInstallOptions): Promise { + const pathAlias = await getPathAlias({ + projectPath: path, + isTypescriptProject: typescript, + extraDirectories: ["app"], + }); + const templatesDir = pathModule.join(templatesPath(), "remix"); + const appFolder = pathModule.join(path, "app"); + const fileExtension = typescript ? ".ts" : ".js"; + + //create app/api.trigger.js + const apiRoutePath = pathModule.join(appFolder, "routes", `api.trigger${fileExtension}`); + const apiRouteResult = await createFileFromTemplate({ + templatePath: pathModule.join(templatesDir, "apiRoute.js"), + replacements: { + routePathPrefix: pathAlias ? pathAlias + "/" : "../../", + }, + outputPath: apiRoutePath, + }); + if (!apiRouteResult.success) { + throw new Error("Failed to create API route file"); + } + logger.success(`βœ… Created API route at ${apiRoutePath}`); + + //app/trigger.js + const triggerFilePath = pathModule.join(appFolder, `trigger${fileExtension}`); + const triggerResult = await createFileFromTemplate({ + templatePath: pathModule.join(templatesDir, "trigger.js"), + replacements: { + endpointSlug, + }, + outputPath: triggerFilePath, + }); + if (!triggerResult.success) { + throw new Error("Failed to create trigger file"); + } + logger.success(`βœ… Created Trigger client at ${triggerFilePath}`); + + //app/jobs/example.server.js + const exampleJobFilePath = pathModule.join(appFolder, "jobs", `example.server${fileExtension}`); + const exampleJobResult = await createFileFromTemplate({ + templatePath: pathModule.join(templatesDir, "exampleJob.js"), + replacements: { + jobsPathPrefix: pathAlias ? pathAlias + "/" : "../", + }, + outputPath: exampleJobFilePath, + }); + if (!exampleJobResult.success) { + throw new Error("Failed to create example job file"); + } + logger.success(`βœ… Created example job at ${exampleJobFilePath}`); + } + + async postInstall(path: string, options: ProjectInstallOptions): Promise {} +} diff --git a/packages/cli/src/frameworks/remix/remix.test.ts b/packages/cli/src/frameworks/remix/remix.test.ts new file mode 100644 index 00000000000..9f4ee146d27 --- /dev/null +++ b/packages/cli/src/frameworks/remix/remix.test.ts @@ -0,0 +1,106 @@ +import mock from "mock-fs"; +import { Remix } from "."; +import { getFramework } from ".."; +import { pathExists } from "../../utils/fileSystem"; + +afterEach(() => { + mock.restore(); +}); + +describe("Remix project detection", () => { + test("has dependency", async () => { + mock({ + "package.json": JSON.stringify({ dependencies: { "@remix-run/express": "1.0.0" } }), + }); + + const framework = await getFramework("", "npm"); + expect(framework?.id).toEqual("remix"); + }); + + test("no dependency, has remix.config.js", async () => { + mock({ + "package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }), + "remix.config.js": "module.exports = {}", + }); + + const framework = await getFramework("", "npm"); + expect(framework?.id).toEqual("remix"); + }); + + test("no dependency, no remix.config.js", async () => { + mock({ + "package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }), + }); + + const framework = await getFramework("", "npm"); + expect(framework?.id).not.toEqual("remix"); + }); +}); + +// describe("install", () => { +// test("src/pages + javascript", async () => { +// mock({ +// "src/pages": {}, +// }); + +// const projectType = await detectPagesOrAppDir(""); +// expect(projectType).toEqual("pages"); + +// const nextJs = new NextJs(); +// await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); +// expect(await pathExists("src/trigger.js")).toEqual(true); +// expect(await pathExists("src/pages/api/trigger.js")).toEqual(true); +// expect(await pathExists("src/jobs/index.js")).toEqual(true); +// expect(await pathExists("src/jobs/examples.js")).toEqual(true); +// }); + +// test("pages + javascript", async () => { +// mock({ +// pages: {}, +// }); + +// const projectType = await detectPagesOrAppDir(""); +// expect(projectType).toEqual("pages"); + +// const nextJs = new NextJs(); +// await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); +// expect(await pathExists("trigger.js")).toEqual(true); +// expect(await pathExists("pages/api/trigger.js")).toEqual(true); +// expect(await pathExists("jobs/index.js")).toEqual(true); +// expect(await pathExists("jobs/examples.js")).toEqual(true); +// }); + +// test("src/pages + typescript", async () => { +// mock({ +// "src/pages": {}, +// "tsconfig.json": JSON.stringify({}), +// }); + +// const projectType = await detectPagesOrAppDir(""); +// expect(projectType).toEqual("pages"); + +// const nextJs = new NextJs(); +// await nextJs.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); +// expect(await pathExists("src/trigger.ts")).toEqual(true); +// expect(await pathExists("src/pages/api/trigger.ts")).toEqual(true); +// expect(await pathExists("src/jobs/index.ts")).toEqual(true); +// expect(await pathExists("src/jobs/examples.ts")).toEqual(true); +// }); + +// test("pages + typescript", async () => { +// mock({ +// pages: {}, +// "tsconfig.json": JSON.stringify({}), +// }); + +// const projectType = await detectPagesOrAppDir(""); +// expect(projectType).toEqual("pages"); + +// const nextJs = new NextJs(); +// await nextJs.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); +// expect(await pathExists("trigger.ts")).toEqual(true); +// expect(await pathExists("pages/api/trigger.ts")).toEqual(true); +// expect(await pathExists("jobs/index.ts")).toEqual(true); +// expect(await pathExists("jobs/examples.ts")).toEqual(true); +// }); +// }); diff --git a/packages/cli/src/templates/remix/apiRoute.js b/packages/cli/src/templates/remix/apiRoute.js new file mode 100644 index 00000000000..57386f3546d --- /dev/null +++ b/packages/cli/src/templates/remix/apiRoute.js @@ -0,0 +1,8 @@ +import { createRemixRoute } from "@trigger.dev/remix"; +import { client } from "${routePathPrefix}trigger"; + +// Remix will automatically strip files with side effects +// So you need to *export* your Job definitions like this: +export * from "${routePathPrefix}jobs/example.server"; + +export const { action } = createRemixRoute(client); diff --git a/packages/cli/src/templates/remix/exampleJob.js b/packages/cli/src/templates/remix/exampleJob.js new file mode 100644 index 00000000000..ff0fbdc800e --- /dev/null +++ b/packages/cli/src/templates/remix/exampleJob.js @@ -0,0 +1,27 @@ +import { eventTrigger } from "@trigger.dev/sdk"; +import { client } from "${jobsPathPrefix}trigger"; + +// Your first job +// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline +client.defineJob({ + // This is the unique identifier for your Job, it must be unique across all Jobs in your project + id: "example-job", + name: "Example Job: a joke with a delay", + version: "0.0.1", + // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + // This logs a message to the console + await io.logger.info("πŸ§ͺ Example Job: a joke with a delay"); + await io.logger.info("How do you comfort a JavaScript bug?"); + // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year + await io.wait("Wait 5 seconds for the punchline...", 5); + await io.logger.info("You console it! 🀦"); + await io.logger.info( + "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" + ); + // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job + }, +}); diff --git a/packages/cli/src/templates/remix/trigger.js b/packages/cli/src/templates/remix/trigger.js new file mode 100644 index 00000000000..578cfb089b9 --- /dev/null +++ b/packages/cli/src/templates/remix/trigger.js @@ -0,0 +1,7 @@ +import { TriggerClient } from "@trigger.dev/sdk"; + +export const client = new TriggerClient({ + id: "${endpointSlug}", + apiKey: process.env.TRIGGER_API_KEY, + apiUrl: process.env.TRIGGER_API_URL, +}); From 290d4cd1783265909379657e233cf6c738955101 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 12:02:46 +0100 Subject: [PATCH 36/53] Tests for Remix install --- .../cli/src/frameworks/remix/remix.test.ts | 91 ++++++------------- 1 file changed, 27 insertions(+), 64 deletions(-) diff --git a/packages/cli/src/frameworks/remix/remix.test.ts b/packages/cli/src/frameworks/remix/remix.test.ts index 9f4ee146d27..7ec2ef04885 100644 --- a/packages/cli/src/frameworks/remix/remix.test.ts +++ b/packages/cli/src/frameworks/remix/remix.test.ts @@ -37,70 +37,33 @@ describe("Remix project detection", () => { }); }); -// describe("install", () => { -// test("src/pages + javascript", async () => { -// mock({ -// "src/pages": {}, -// }); - -// const projectType = await detectPagesOrAppDir(""); -// expect(projectType).toEqual("pages"); - -// const nextJs = new NextJs(); -// await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); -// expect(await pathExists("src/trigger.js")).toEqual(true); -// expect(await pathExists("src/pages/api/trigger.js")).toEqual(true); -// expect(await pathExists("src/jobs/index.js")).toEqual(true); -// expect(await pathExists("src/jobs/examples.js")).toEqual(true); -// }); - -// test("pages + javascript", async () => { -// mock({ -// pages: {}, -// }); - -// const projectType = await detectPagesOrAppDir(""); -// expect(projectType).toEqual("pages"); - -// const nextJs = new NextJs(); -// await nextJs.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); -// expect(await pathExists("trigger.js")).toEqual(true); -// expect(await pathExists("pages/api/trigger.js")).toEqual(true); -// expect(await pathExists("jobs/index.js")).toEqual(true); -// expect(await pathExists("jobs/examples.js")).toEqual(true); -// }); - -// test("src/pages + typescript", async () => { -// mock({ -// "src/pages": {}, -// "tsconfig.json": JSON.stringify({}), -// }); - -// const projectType = await detectPagesOrAppDir(""); -// expect(projectType).toEqual("pages"); - -// const nextJs = new NextJs(); -// await nextJs.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); -// expect(await pathExists("src/trigger.ts")).toEqual(true); -// expect(await pathExists("src/pages/api/trigger.ts")).toEqual(true); -// expect(await pathExists("src/jobs/index.ts")).toEqual(true); -// expect(await pathExists("src/jobs/examples.ts")).toEqual(true); -// }); +describe("install", () => { + test("javascript", async () => { + mock({ + app: { + routes: {}, + }, + }); -// test("pages + typescript", async () => { -// mock({ -// pages: {}, -// "tsconfig.json": JSON.stringify({}), -// }); + const remix = new Remix(); + await remix.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("app/trigger.js")).toEqual(true); + expect(await pathExists("app/routes/api.trigger.js")).toEqual(true); + expect(await pathExists("app/jobs/example.server.js")).toEqual(true); + }); -// const projectType = await detectPagesOrAppDir(""); -// expect(projectType).toEqual("pages"); + test("typescript", async () => { + mock({ + app: { + routes: {}, + }, + "tsconfig.json": JSON.stringify({}), + }); -// const nextJs = new NextJs(); -// await nextJs.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); -// expect(await pathExists("trigger.ts")).toEqual(true); -// expect(await pathExists("pages/api/trigger.ts")).toEqual(true); -// expect(await pathExists("jobs/index.ts")).toEqual(true); -// expect(await pathExists("jobs/examples.ts")).toEqual(true); -// }); -// }); + const remix = new Remix(); + await remix.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); + expect(await pathExists("app/trigger.ts")).toEqual(true); + expect(await pathExists("app/routes/api.trigger.ts")).toEqual(true); + expect(await pathExists("app/jobs/example.server.ts")).toEqual(true); + }); +}); From 3e0d05f3023cbbf8c43826bc054bac0650c6ed1b Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 13:35:23 +0100 Subject: [PATCH 37/53] Replaced references to Next.js --- packages/cli/README.md | 6 +++--- packages/cli/src/cli/index.ts | 8 ++++---- packages/cli/src/commands/dev.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 2c828b13a57..e4cb3940e39 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,10 +1,10 @@ -## ✨ @trigger.dev/cli - Initialize your Next.js project to start using Trigger.dev +## ✨ @trigger.dev/cli - Initialize your project to start using Trigger.dev -Trigger.dev is an open source platform that makes it easy to create event-driven background tasks directly your Next.js project. +Trigger.dev is an open source platform that makes it easy to create event-driven background tasks directly in your existing project. ## πŸ’» Usage -To initialize your Next.js project using `@trigger.dev/cli`, run any of the following three commands and answer the prompts: +To initialize your project using `@trigger.dev/cli`, run any of the following three commands and answer the prompts: ### npm diff --git a/packages/cli/src/cli/index.ts b/packages/cli/src/cli/index.ts index 8ce33ae2421..593c4d1965a 100644 --- a/packages/cli/src/cli/index.ts +++ b/packages/cli/src/cli/index.ts @@ -17,12 +17,12 @@ 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 ", "The path to the Next.js project", ".") + .description("Initialize Trigger.dev in your project") + .option("-p, --project-path ", "The path to the project", ".") .option("-k, --api-key ", "The development API key to use for the project.") .option( "-e, --endpoint-id ", - "The unique ID for the endpoint to use for this project. (e.g. my-nextjs-project)" + "The unique ID for the endpoint to use for this project. (e.g. my-project)" ) .option( "-t, --trigger-url ", @@ -41,7 +41,7 @@ program program .command("dev") - .description("Tunnel your local Next.js project to Trigger.dev and start running jobs") + .description("Tunnel your local project to Trigger.dev and start running jobs") .argument("[path]", "The path to the project", ".") .option("-p, --port ", "Override the local port your server is on") .option("-H, --hostname ", "Override the hostname on which the application is served") diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index b845ab7c0fd..d0f83381791 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -81,7 +81,7 @@ export async function devCommand(path: string, anyOptions: any) { logger.success(`βœ”οΈ [trigger.dev] Found API Key in ${envFile} file`); - logger.info(` [trigger.dev] Looking for Next.js site on port ${resolvedOptions.port}`); + logger.info(` [trigger.dev] Looking for your app on port ${resolvedOptions.port}`); const localEndpointHandlerUrl = `http://${resolvedOptions.hostname}:${resolvedOptions.port}${resolvedOptions.handlerPath}`; @@ -96,7 +96,7 @@ export async function devCommand(path: string, anyOptions: any) { }); } catch (err) { logger.error( - `❌ [trigger.dev] No server found on port ${resolvedOptions.port}. Make sure your Next.js app is running and try again.` + `❌ [trigger.dev] No server found on port ${resolvedOptions.port}. Make sure your app is running and try again.` ); telemetryClient.dev.failed("no_server_found", resolvedOptions); return; From 31b71abf9e61bda1bd719297cd5ae14f103cfa80 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 13:56:27 +0100 Subject: [PATCH 38/53] =?UTF-8?q?Use=20a=20green=20=E2=9C=94=EF=B8=8F=20in?= =?UTF-8?q?stead=20of=20=E2=9C=85=20in=20the=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/commands/createIntegration.ts | 2 +- packages/cli/src/commands/init.ts | 6 +++--- packages/cli/src/commands/update.ts | 4 ++-- packages/cli/src/utils/env.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/commands/createIntegration.ts b/packages/cli/src/commands/createIntegration.ts index 923226a55e8..a1ffb07d72a 100644 --- a/packages/cli/src/commands/createIntegration.ts +++ b/packages/cli/src/commands/createIntegration.ts @@ -207,7 +207,7 @@ export default defineConfig([ // Install the dependencies await installDependencies(resolvedPath); - logger.success(`βœ… Successfully initialized ${resolvedOptions.packageName} at ${resolvedPath}`); + logger.success(`βœ” Successfully initialized ${resolvedOptions.packageName} at ${resolvedPath}`); logger.info("Next steps:"); logger.info(` 1. If you generated code, double check it for errors.`); logger.info( diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index e3815c0c958..226d70c57bf 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -57,7 +57,7 @@ export const initCommand = async (options: InitCommandOptions) => { telemetryClient.init.failed("not_supported_project", options); return; } - logger.success(`βœ… Detected ${framework.name} project`); + logger.success(`βœ” Detected ${framework.name} project`); const hasGitChanges = await detectGitChanges(resolvedPath); if (hasGitChanges) { @@ -141,7 +141,7 @@ async function printNextSteps( ) { const projectUrl = `${options.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`; - logger.success(`βœ… Successfully initialized Trigger.dev!`); + logger.success(`βœ” Successfully initialized Trigger.dev!`); logger.info("Next steps:"); logger.info(` 1. Run your ${framework.name} project locally with '${packageManager} run dev'`); @@ -170,7 +170,7 @@ async function addConfigurationToPackageJson(path: string, options: ResolvedOpti const pkgJsonPath = pathModule.join(path, "package.json"); await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2)); - logger.success(`βœ… Wrote trigger.dev config to package.json`); + logger.success(`βœ” Wrote trigger.dev config to package.json`); } type OptionsAfterPrompts = Required> & { diff --git a/packages/cli/src/commands/update.ts b/packages/cli/src/commands/update.ts index 239f1c26b15..ff6ed559ff1 100644 --- a/packages/cli/src/commands/update.ts +++ b/packages/cli/src/commands/update.ts @@ -55,7 +55,7 @@ export async function updateCommand(projectPath: string) { // If there are no @trigger.dev packages if (triggerPackages.length === 0) { - logger.success(`βœ… All @trigger.dev/* packages are up to date.`); + logger.success(`βœ” All @trigger.dev/* packages are up to date.`); return; } @@ -65,7 +65,7 @@ export async function updateCommand(projectPath: string) { // If no packages require any updation if (packagesToUpdate.length === 0) { - logger.success(`βœ… All @trigger.dev/* packages are up to date.`); + logger.success(`βœ” All @trigger.dev/* packages are up to date.`); return; } diff --git a/packages/cli/src/utils/env.ts b/packages/cli/src/utils/env.ts index ec0b55dbf1b..9750cf64f67 100644 --- a/packages/cli/src/utils/env.ts +++ b/packages/cli/src/utils/env.ts @@ -66,10 +66,10 @@ async function setEnvironmentVariable( await fs.writeFile(path, updatedEnvFileContent); - logger.success(`βœ… Set ${variableName}=${renderer(value)} in ${fileName}`); + logger.success(`βœ” Set ${variableName}=${renderer(value)} in ${fileName}`); } else { await fs.appendFile(path, `\n${variableName}=${value}`); - logger.success(`βœ… Added ${variableName}=${renderer(value)} to ${fileName}`); + logger.success(`βœ” Added ${variableName}=${renderer(value)} to ${fileName}`); } } From 315097c6282a412e36daf60baff4e045bba9d093 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 14:16:29 +0100 Subject: [PATCH 39/53] Support for multiple hostnames --- packages/cli/src/commands/dev.ts | 105 ++++++++++++------ packages/cli/src/frameworks/index.ts | 2 +- packages/cli/src/frameworks/nextjs/index.ts | 12 +- packages/cli/src/frameworks/remix/index.ts | 12 +- .../cli/src/frameworks/remix/remix.test.ts | 4 +- 5 files changed, 87 insertions(+), 48 deletions(-) diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index d0f83381791..350c1586d6d 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -17,6 +17,7 @@ import chalk from "chalk"; import { getUserPackageManager } from "../utils/getUserPkgManager"; import { Framework, getFramework } from "../frameworks"; import { getEnvFilename } from "../utils/env"; +import { verify } from "crypto"; const asyncExecFile = util.promisify(childProcess.execFile); @@ -29,7 +30,10 @@ export const DevCommandOptionsSchema = z.object({ }); export type DevCommandOptions = z.infer; -type ResolvedOptions = Omit, "clientId"> & { clientId?: string }; +type ResolvedOptions = Omit, "clientId" | "hostname"> & { + clientId?: string; + hostname?: string; +}; const throttleTimeMs = 1000; @@ -71,51 +75,32 @@ export async function devCommand(path: string, anyOptions: any) { // Read from .env.local or .env to get the TRIGGER_API_KEY and TRIGGER_API_URL const apiDetails = await getTriggerApiDetails(resolvedPath, resolvedOptions.envFile); - if (!apiDetails) { telemetryClient.dev.failed("missing_api_key", resolvedOptions); return; } - const { apiUrl, envFile, apiKey } = apiDetails; - logger.success(`βœ”οΈ [trigger.dev] Found API Key in ${envFile} file`); - logger.info(` [trigger.dev] Looking for your app on port ${resolvedOptions.port}`); - - const localEndpointHandlerUrl = `http://${resolvedOptions.hostname}:${resolvedOptions.port}${resolvedOptions.handlerPath}`; - - try { - await fetch(localEndpointHandlerUrl, { - method: "POST", - headers: { - "x-trigger-api-key": apiKey, - "x-trigger-action": "PING", - "x-trigger-endpoint-id": endpointId, - }, - }); - } catch (err) { - logger.error( - `❌ [trigger.dev] No server found on port ${resolvedOptions.port}. Make sure your app is running and try again.` - ); + //verify that the endpoint can be reached + const verifiedEndpoint = await verifyEndpoint(resolvedOptions, endpointId, apiKey, framework); + if (!verifiedEndpoint) { telemetryClient.dev.failed("no_server_found", resolvedOptions); return; } + const { hostname, port, handlerPath } = verifiedEndpoint; + telemetryClient.dev.serverRunning(path, resolvedOptions); // Setup tunnel - const endpointUrl = await resolveEndpointUrl( - apiUrl, - resolvedOptions.port, - resolvedOptions.hostname - ); + const endpointUrl = await resolveEndpointUrl(apiUrl, port, hostname); if (!endpointUrl) { telemetryClient.dev.failed("failed_to_create_tunnel", resolvedOptions); return; } - const endpointHandlerUrl = `${endpointUrl}${resolvedOptions.handlerPath}`; + const endpointHandlerUrl = `${endpointUrl}${handlerPath}`; telemetryClient.dev.tunnelRunning(path, resolvedOptions); const connectingSpinner = ora(`[trigger.dev] Registering endpoint ${endpointHandlerUrl}...`); @@ -132,7 +117,7 @@ export async function devCommand(path: string, anyOptions: any) { const apiDetails = await getTriggerApiDetails(resolvedPath, envFile); if (!apiDetails) { - connectingSpinner.fail(`❌ [trigger.dev] Failed to connect: Missing API Key`); + connectingSpinner.fail(`Γ— [trigger.dev] Failed to connect: Missing API Key`); logger.info(`Will attempt again on the next file change…`); attemptCount = 0; return; @@ -144,7 +129,7 @@ export async function devCommand(path: string, anyOptions: any) { 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.` + `Γ— The API key you provided is not authorized. Try visiting your dashboard to get a new API key.` ); telemetryClient.dev.failed("invalid_api_key", resolvedOptions); @@ -178,7 +163,7 @@ export async function devCommand(path: string, anyOptions: any) { attemptCount++; if (attemptCount === 10 || !result.retryable) { - connectingSpinner.fail(`🚨 Failed to connect: ${result.error}`); + connectingSpinner.fail(`Γ— Failed to connect: ${result.error}`); logger.info(`Will attempt again on the next file change…`); attemptCount = 0; @@ -221,7 +206,7 @@ export async function devCommand(path: string, anyOptions: any) { throttle(refresh, throttleTimeMs); } -export async function resolveOptions( +async function resolveOptions( framework: Framework | undefined, path: string, unresolvedOptions: DevCommandOptions @@ -239,17 +224,71 @@ export async function resolveOptions( //get env filename const envName = await getEnvFilename(path, framework.possibleEnvFilenames()); - framework.defaultHostname; return { port: unresolvedOptions.port ?? framework.defaultPort ?? 3000, - hostname: unresolvedOptions.hostname ?? framework.defaultHostname ?? "localhost", + hostname: unresolvedOptions.hostname, envFile: unresolvedOptions.envFile ?? envName ?? ".env", handlerPath: unresolvedOptions.handlerPath, clientId: unresolvedOptions.clientId, }; } +async function verifyEndpoint( + resolvedOptions: ResolvedOptions, + endpointId: string, + apiKey: string, + framework?: Framework +) { + //create list of hostnames to try + const hostnames = []; + if (resolvedOptions.hostname) { + hostnames.push(resolvedOptions.hostname); + } + if (framework) { + hostnames.push(...framework.defaultHostnames); + } + + //try each hostname + for (const hostname of hostnames) { + const localEndpointHandlerUrl = `http://${hostname}:${resolvedOptions.port}${resolvedOptions.handlerPath}`; + + const spinner = ora( + `[trigger.dev] Looking for your trigger endpoint: ${localEndpointHandlerUrl}` + ).start(); + + try { + const response = await fetch(localEndpointHandlerUrl, { + method: "POST", + headers: { + "x-trigger-api-key": apiKey, + "x-trigger-action": "PING", + "x-trigger-endpoint-id": endpointId, + }, + }); + + if (!response.ok || response.status !== 200) { + spinner.fail( + `[trigger.dev] Found a server, but the trigger endpoint responded with ${response.status}.` + ); + continue; + } + + spinner.succeed(`[trigger.dev] Found your trigger endpoint.`); + return { hostname, port: resolvedOptions.port, handlerPath: resolvedOptions.handlerPath }; + } catch (err) { + logger.error( + `βœ– [trigger.dev] No server found (${localEndpointHandlerUrl}). Make sure your app is running and try again.` + ); + spinner.fail( + `No server found (${localEndpointHandlerUrl}). Make sure your app is running and try again.` + ); + } + } + + return; +} + export async function checkForOutdatedPackages(path: string) { const updates = (await ncuRun({ packageFile: `${path}/package.json`, diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts index 06298207375..c41431487c8 100644 --- a/packages/cli/src/frameworks/index.ts +++ b/packages/cli/src/frameworks/index.ts @@ -12,7 +12,7 @@ export type ProjectInstallOptions = { export interface Framework { id: string; name: string; - defaultHostname: string; + defaultHostnames: string[]; defaultPort: number; isMatch(path: string, packageManager: PackageManager): Promise; dependencies(): Promise; diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 10d586267c6..94f616212b5 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -14,7 +14,7 @@ import { detectMiddlewareUsage } from "./middleware"; export class NextJs implements Framework { id = "nextjs"; name = "Next.js"; - defaultHostname = "localhost"; + defaultHostnames = ["localhost"]; defaultPort = 3000; async isMatch(path: string, packageManager: PackageManager): Promise { @@ -128,7 +128,7 @@ async function createTriggerPageRoute( if (!apiRouteResult.success) { throw new Error("Failed to create API route file"); } - logger.success(`βœ… Created API route at ${apiRoutePath}`); + logger.success(`βœ” Created API route at ${apiRoutePath}`); await createJobsAndTriggerFile(path, endpointSlug, fileExtension, pathAlias, templatesDir); } @@ -154,7 +154,7 @@ async function createTriggerAppRoute( if (!apiRouteResult.success) { throw new Error("Failed to create API route file"); } - logger.success(`βœ… Created API route at ${apiRoutePath}`); + logger.success(`βœ” Created API route at ${apiRoutePath}`); await createJobsAndTriggerFile(path, endpointSlug, fileExtension, pathAlias, templatesDir); } @@ -178,7 +178,7 @@ async function createJobsAndTriggerFile( if (!triggerResult.success) { throw new Error("Failed to create trigger file"); } - logger.success(`βœ… Created Trigger client at ${triggerFilePath}`); + logger.success(`βœ” Created Trigger client at ${triggerFilePath}`); //example jobs const exampleDirectory = pathModule.join(path, "jobs"); @@ -195,7 +195,7 @@ async function createJobsAndTriggerFile( if (!exampleJobResult.success) { throw new Error("Failed to create example job file"); } - logger.success(`βœ… Created example job at ${exampleJobFilePath}`); + logger.success(`βœ” Created example job at ${exampleJobFilePath}`); //jobs/index.js or src/jobs/index.js const jobsIndexFilePath = pathModule.join(exampleDirectory, `index${fileExtension}`); @@ -209,5 +209,5 @@ async function createJobsAndTriggerFile( if (!jobsIndexResult.success) { throw new Error("Failed to create jobs index file"); } - logger.success(`βœ… Created jobs index at ${jobsIndexFilePath}`); + logger.success(`βœ” Created jobs index at ${jobsIndexFilePath}`); } diff --git a/packages/cli/src/frameworks/remix/index.ts b/packages/cli/src/frameworks/remix/index.ts index 55497fbb2ad..7802e518bad 100644 --- a/packages/cli/src/frameworks/remix/index.ts +++ b/packages/cli/src/frameworks/remix/index.ts @@ -12,7 +12,7 @@ import { readPackageJson } from "../../utils/readPackageJson"; export class Remix implements Framework { id = "remix"; name = "Remix"; - defaultHostname = "localhost"; + defaultHostnames = ["localhost"]; defaultPort = 3000; async isMatch(path: string, packageManager: PackageManager): Promise { @@ -71,10 +71,10 @@ export class Remix implements Framework { if (!apiRouteResult.success) { throw new Error("Failed to create API route file"); } - logger.success(`βœ… Created API route at ${apiRoutePath}`); + logger.success(`βœ” Created API route at ${apiRoutePath}`); - //app/trigger.js - const triggerFilePath = pathModule.join(appFolder, `trigger${fileExtension}`); + //app/trigger.server.js + const triggerFilePath = pathModule.join(appFolder, `trigger.server${fileExtension}`); const triggerResult = await createFileFromTemplate({ templatePath: pathModule.join(templatesDir, "trigger.js"), replacements: { @@ -85,7 +85,7 @@ export class Remix implements Framework { if (!triggerResult.success) { throw new Error("Failed to create trigger file"); } - logger.success(`βœ… Created Trigger client at ${triggerFilePath}`); + logger.success(`βœ” Created Trigger client at ${triggerFilePath}`); //app/jobs/example.server.js const exampleJobFilePath = pathModule.join(appFolder, "jobs", `example.server${fileExtension}`); @@ -99,7 +99,7 @@ export class Remix implements Framework { if (!exampleJobResult.success) { throw new Error("Failed to create example job file"); } - logger.success(`βœ… Created example job at ${exampleJobFilePath}`); + logger.success(`βœ” Created example job at ${exampleJobFilePath}`); } async postInstall(path: string, options: ProjectInstallOptions): Promise {} diff --git a/packages/cli/src/frameworks/remix/remix.test.ts b/packages/cli/src/frameworks/remix/remix.test.ts index 7ec2ef04885..bbee3e3750d 100644 --- a/packages/cli/src/frameworks/remix/remix.test.ts +++ b/packages/cli/src/frameworks/remix/remix.test.ts @@ -47,7 +47,7 @@ describe("install", () => { const remix = new Remix(); await remix.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" }); - expect(await pathExists("app/trigger.js")).toEqual(true); + expect(await pathExists("app/trigger.server.js")).toEqual(true); expect(await pathExists("app/routes/api.trigger.js")).toEqual(true); expect(await pathExists("app/jobs/example.server.js")).toEqual(true); }); @@ -62,7 +62,7 @@ describe("install", () => { const remix = new Remix(); await remix.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" }); - expect(await pathExists("app/trigger.ts")).toEqual(true); + expect(await pathExists("app/trigger.server.ts")).toEqual(true); expect(await pathExists("app/routes/api.trigger.ts")).toEqual(true); expect(await pathExists("app/jobs/example.server.ts")).toEqual(true); }); From 351586bd4b750a7f281fdbe0e7fdb73af943a637 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 14:34:27 +0100 Subject: [PATCH 40/53] Tunneling can now use the hostname and port --- packages/cli/src/commands/dev.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 350c1586d6d..ed3d7313b34 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -117,7 +117,7 @@ export async function devCommand(path: string, anyOptions: any) { const apiDetails = await getTriggerApiDetails(resolvedPath, envFile); if (!apiDetails) { - connectingSpinner.fail(`Γ— [trigger.dev] Failed to connect: Missing API Key`); + connectingSpinner.fail(`[trigger.dev] Failed to connect: Missing API Key`); logger.info(`Will attempt again on the next file change…`); attemptCount = 0; return; @@ -163,7 +163,7 @@ export async function devCommand(path: string, anyOptions: any) { attemptCount++; if (attemptCount === 10 || !result.retryable) { - connectingSpinner.fail(`Γ— Failed to connect: ${result.error}`); + connectingSpinner.fail(`Failed to connect: ${result.error}`); logger.info(`Will attempt again on the next file change…`); attemptCount = 0; @@ -274,14 +274,11 @@ async function verifyEndpoint( continue; } - spinner.succeed(`[trigger.dev] Found your trigger endpoint.`); + spinner.succeed(`[trigger.dev] Found your trigger endpoint: ${localEndpointHandlerUrl}`); return { hostname, port: resolvedOptions.port, handlerPath: resolvedOptions.handlerPath }; } catch (err) { - logger.error( - `βœ– [trigger.dev] No server found (${localEndpointHandlerUrl}). Make sure your app is running and try again.` - ); spinner.fail( - `No server found (${localEndpointHandlerUrl}). Make sure your app is running and try again.` + `[trigger.dev] No server found (${localEndpointHandlerUrl}). Make sure your app is running and try again.` ); } } @@ -331,14 +328,14 @@ export async function getEndpointIdFromPackageJson(path: string, options: DevCom async function resolveEndpointUrl(apiUrl: string, port: number, hostname: string) { const apiURL = new URL(apiUrl); - if (apiURL.hostname === "localhost") { + //if the API is localhost and the hostname is localhost + if (apiURL.hostname === "localhost" && hostname === "localhost") { return `http://${hostname}:${port}`; } // Setup tunnel const tunnelSpinner = ora(`πŸš‡ Creating tunnel`).start(); - - const tunnelUrl = await createTunnel(port, tunnelSpinner); + const tunnelUrl = await createTunnel(hostname, port, tunnelSpinner); if (tunnelUrl) { tunnelSpinner.succeed(`πŸš‡ Created tunnel: ${tunnelUrl}`); @@ -347,9 +344,9 @@ async function resolveEndpointUrl(apiUrl: string, port: number, hostname: string return tunnelUrl; } -async function createTunnel(port: number, spinner: Ora) { +async function createTunnel(hostname: string, port: number, spinner: Ora) { try { - return await ngrok.connect(port); + return await ngrok.connect({ addr: `${hostname}:${port}` }); } catch (error: any) { if ( typeof error.message === "string" && From 7dbf0fbde7b9f23e4fe7bfdca0cc5e4d9fced3fd Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 15:49:59 +0100 Subject: [PATCH 41/53] Work on multiple ports --- packages/cli/src/commands/dev.ts | 49 ++++++++++++++------- packages/cli/src/frameworks/index.ts | 2 +- packages/cli/src/frameworks/nextjs/index.ts | 2 +- packages/cli/src/frameworks/remix/index.ts | 2 +- packages/cli/src/utils/requiredKeys.ts | 4 ++ 5 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 packages/cli/src/utils/requiredKeys.ts diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index ed3d7313b34..93dfc3e91d1 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -1,23 +1,23 @@ +import chalk from "chalk"; import childProcess from "child_process"; import chokidar from "chokidar"; import fs from "fs/promises"; import ngrok from "ngrok"; -import fetch from "../utils/fetchUseProxy"; +import { run as ncuRun } from "npm-check-updates"; import ora, { Ora } from "ora"; import pathModule from "path"; import util from "util"; import { z } from "zod"; +import { Framework, getFramework } from "../frameworks"; import { telemetryClient } from "../telemetry/telemetry"; +import { getEnvFilename } from "../utils/env"; +import fetch from "../utils/fetchUseProxy"; import { getTriggerApiDetails } from "../utils/getTriggerApiDetails"; +import { getUserPackageManager } from "../utils/getUserPkgManager"; import { logger } from "../utils/logger"; import { resolvePath } from "../utils/parseNameAndPath"; +import { RequireKeys } from "../utils/requiredKeys"; import { TriggerApi } from "../utils/triggerApi"; -import { run as ncuRun } from "npm-check-updates"; -import chalk from "chalk"; -import { getUserPackageManager } from "../utils/getUserPkgManager"; -import { Framework, getFramework } from "../frameworks"; -import { getEnvFilename } from "../utils/env"; -import { verify } from "crypto"; const asyncExecFile = util.promisify(childProcess.execFile); @@ -30,10 +30,7 @@ export const DevCommandOptionsSchema = z.object({ }); export type DevCommandOptions = z.infer; -type ResolvedOptions = Omit, "clientId" | "hostname"> & { - clientId?: string; - hostname?: string; -}; +type ResolvedOptions = RequireKeys; const throttleTimeMs = 1000; @@ -226,7 +223,7 @@ async function resolveOptions( const envName = await getEnvFilename(path, framework.possibleEnvFilenames()); return { - port: unresolvedOptions.port ?? framework.defaultPort ?? 3000, + port: unresolvedOptions.port, hostname: unresolvedOptions.hostname, envFile: unresolvedOptions.envFile ?? envName ?? ".env", handlerPath: unresolvedOptions.handlerPath, @@ -247,11 +244,33 @@ async function verifyEndpoint( } if (framework) { hostnames.push(...framework.defaultHostnames); + } else { + hostnames.push("localhost"); } - //try each hostname + //create list of ports to try + const ports = []; + if (resolvedOptions.port) { + ports.push(resolvedOptions.port); + } + if (framework) { + ports.push(...framework.defaultPorts); + } else { + ports.push(3000); + } + + //create list of urls to try + const urls: { hostname: string; port: number }[] = []; for (const hostname of hostnames) { - const localEndpointHandlerUrl = `http://${hostname}:${resolvedOptions.port}${resolvedOptions.handlerPath}`; + for (const port of ports) { + urls.push({ hostname, port }); + } + } + + //try each hostname + for (const url of urls) { + const { hostname, port } = url; + const localEndpointHandlerUrl = `http://${hostname}:${port}${resolvedOptions.handlerPath}`; const spinner = ora( `[trigger.dev] Looking for your trigger endpoint: ${localEndpointHandlerUrl}` @@ -275,7 +294,7 @@ async function verifyEndpoint( } spinner.succeed(`[trigger.dev] Found your trigger endpoint: ${localEndpointHandlerUrl}`); - return { hostname, port: resolvedOptions.port, handlerPath: resolvedOptions.handlerPath }; + return { hostname, port, handlerPath: resolvedOptions.handlerPath }; } catch (err) { spinner.fail( `[trigger.dev] No server found (${localEndpointHandlerUrl}). Make sure your app is running and try again.` diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts index c41431487c8..4d6cef47ba8 100644 --- a/packages/cli/src/frameworks/index.ts +++ b/packages/cli/src/frameworks/index.ts @@ -13,7 +13,7 @@ export interface Framework { id: string; name: string; defaultHostnames: string[]; - defaultPort: number; + defaultPorts: number[]; isMatch(path: string, packageManager: PackageManager): Promise; dependencies(): Promise; possibleEnvFilenames(): string[]; diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index 94f616212b5..aec8c3cee30 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -15,7 +15,7 @@ export class NextJs implements Framework { id = "nextjs"; name = "Next.js"; defaultHostnames = ["localhost"]; - defaultPort = 3000; + defaultPorts = [3000]; async isMatch(path: string, packageManager: PackageManager): Promise { const hasNextConfigFile = await detectNextConfigFile(path); diff --git a/packages/cli/src/frameworks/remix/index.ts b/packages/cli/src/frameworks/remix/index.ts index 7802e518bad..77957294da7 100644 --- a/packages/cli/src/frameworks/remix/index.ts +++ b/packages/cli/src/frameworks/remix/index.ts @@ -13,7 +13,7 @@ export class Remix implements Framework { id = "remix"; name = "Remix"; defaultHostnames = ["localhost"]; - defaultPort = 3000; + defaultPorts = [3000, 8788, 3333]; async isMatch(path: string, packageManager: PackageManager): Promise { //check for remix.config.js diff --git a/packages/cli/src/utils/requiredKeys.ts b/packages/cli/src/utils/requiredKeys.ts new file mode 100644 index 00000000000..087163e6bc0 --- /dev/null +++ b/packages/cli/src/utils/requiredKeys.ts @@ -0,0 +1,4 @@ +export type RequireKeys = Required> & + Omit extends infer O + ? { [P in keyof O]: O[P] } + : never; From d376f37a5e9fc3201266c790ce3dd2e1b5a13752 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 16:27:48 +0100 Subject: [PATCH 42/53] Improved the error messages. Added some extra pots to Next.js --- packages/cli/src/commands/dev.ts | 12 +++++++----- packages/cli/src/frameworks/nextjs/index.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 93dfc3e91d1..245795e05ac 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -82,6 +82,10 @@ export async function devCommand(path: string, anyOptions: any) { //verify that the endpoint can be reached const verifiedEndpoint = await verifyEndpoint(resolvedOptions, endpointId, apiKey, framework); if (!verifiedEndpoint) { + logger.error( + `βœ– [trigger.dev] Failed to find a valid Trigger.dev endpoint. Make sure your app is running and try again.` + ); + logger.info(` [trigger.dev] You can use -H to specify a hostname, or -p to specify a port.`); telemetryClient.dev.failed("no_server_found", resolvedOptions); return; } @@ -126,7 +130,7 @@ export async function devCommand(path: string, anyOptions: any) { 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.` + `βœ– [trigger.dev] The API key you provided is not authorized. Try visiting your dashboard to get a new API key.` ); telemetryClient.dev.failed("invalid_api_key", resolvedOptions); @@ -288,7 +292,7 @@ async function verifyEndpoint( if (!response.ok || response.status !== 200) { spinner.fail( - `[trigger.dev] Found a server, but the trigger endpoint responded with ${response.status}.` + `[trigger.dev] Server responded with ${response.status} (${localEndpointHandlerUrl}).` ); continue; } @@ -296,9 +300,7 @@ async function verifyEndpoint( spinner.succeed(`[trigger.dev] Found your trigger endpoint: ${localEndpointHandlerUrl}`); return { hostname, port, handlerPath: resolvedOptions.handlerPath }; } catch (err) { - spinner.fail( - `[trigger.dev] No server found (${localEndpointHandlerUrl}). Make sure your app is running and try again.` - ); + spinner.fail(`[trigger.dev] No server found (${localEndpointHandlerUrl}).`); } } diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index aec8c3cee30..afc5f350cf2 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -15,7 +15,7 @@ export class NextJs implements Framework { id = "nextjs"; name = "Next.js"; defaultHostnames = ["localhost"]; - defaultPorts = [3000]; + defaultPorts = [3000, 3001, 3002]; async isMatch(path: string, packageManager: PackageManager): Promise { const hasNextConfigFile = await detectNextConfigFile(path); From df2a64fb821111aa6492a0c5a19304c629d1335c Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 16:46:22 +0100 Subject: [PATCH 43/53] Update the Remix templates to have .server in the imports --- packages/cli/src/templates/remix/apiRoute.js | 2 +- packages/cli/src/templates/remix/exampleJob.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/templates/remix/apiRoute.js b/packages/cli/src/templates/remix/apiRoute.js index 57386f3546d..d0f6d2a92a3 100644 --- a/packages/cli/src/templates/remix/apiRoute.js +++ b/packages/cli/src/templates/remix/apiRoute.js @@ -1,5 +1,5 @@ import { createRemixRoute } from "@trigger.dev/remix"; -import { client } from "${routePathPrefix}trigger"; +import { client } from "${routePathPrefix}trigger.server"; // Remix will automatically strip files with side effects // So you need to *export* your Job definitions like this: diff --git a/packages/cli/src/templates/remix/exampleJob.js b/packages/cli/src/templates/remix/exampleJob.js index ff0fbdc800e..affba011fd9 100644 --- a/packages/cli/src/templates/remix/exampleJob.js +++ b/packages/cli/src/templates/remix/exampleJob.js @@ -1,9 +1,9 @@ import { eventTrigger } from "@trigger.dev/sdk"; -import { client } from "${jobsPathPrefix}trigger"; +import { client } from "${jobsPathPrefix}trigger.server"; // Your first job // This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline -client.defineJob({ +export const job = client.defineJob({ // This is the unique identifier for your Job, it must be unique across all Jobs in your project id: "example-job", name: "Example Job: a joke with a delay", From eed0022e42959994a10616476b835357aee48a0a Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 16:49:06 +0100 Subject: [PATCH 44/53] Remix updated to use server-runtime instead of node. Node v18+ --- packages/remix/package.json | 7 ++++--- packages/remix/src/index.ts | 6 ++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/remix/package.json b/packages/remix/package.json index 9040c157da8..6b669c2dc1b 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -19,7 +19,7 @@ "./package.json": "./package.json" }, "devDependencies": { - "@remix-run/node": "^1.19.3", + "@remix-run/server-runtime": "^2.0.0", "@trigger.dev/tsconfig": "workspace:*", "@types/debug": "^4.1.7", "@types/ws": "^8.5.3", @@ -34,12 +34,13 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.1.3" + "@trigger.dev/sdk": "workspace:^2.1.3", + "@remix-run/server-runtime": ">1.19.0" }, "dependencies": { "debug": "^4.3.4" }, "engines": { - "node": ">=16.8.0" + "node": ">=18.0.0" } } diff --git a/packages/remix/src/index.ts b/packages/remix/src/index.ts index c6cbe6040a2..347e34da8b9 100644 --- a/packages/remix/src/index.ts +++ b/packages/remix/src/index.ts @@ -1,9 +1,8 @@ -import type { ActionArgs } from "@remix-run/node"; -import { json } from "@remix-run/node"; +import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; import type { TriggerClient } from "@trigger.dev/sdk"; export function createRemixRoute(client: TriggerClient) { - const action = async ({ request }: ActionArgs) => { + const action = async ({ request }: ActionFunctionArgs) => { const response = await client.handleRequest(request); if (!response) { @@ -13,5 +12,4 @@ export function createRemixRoute(client: TriggerClient) { return json(response.body, { status: response.status }); }; return { action }; - } From aab3d8ea7c61950ba1af63dfe445807bc9efc7bb Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 17:06:35 +0100 Subject: [PATCH 45/53] Frameworks can specify the watch paths and ignore paths --- packages/cli/src/commands/dev.ts | 18 +++++-------- packages/cli/src/frameworks/index.ts | 28 +++++++++++++++++++-- packages/cli/src/frameworks/nextjs/index.ts | 8 ++++-- packages/cli/src/frameworks/remix/index.ts | 8 ++++-- packages/cli/src/frameworks/watchConfig.ts | 10 ++++++++ 5 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 packages/cli/src/frameworks/watchConfig.ts diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 245795e05ac..13fdf08aeae 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -18,6 +18,7 @@ import { logger } from "../utils/logger"; import { resolvePath } from "../utils/parseNameAndPath"; import { RequireKeys } from "../utils/requiredKeys"; import { TriggerApi } from "../utils/triggerApi"; +import { standardWatchIgnoreRegex, standardWatchFilePaths } from "../frameworks/watchConfig"; const asyncExecFile = util.promisify(childProcess.execFile); @@ -114,7 +115,7 @@ export async function devCommand(path: string, anyOptions: any) { const refreshedEndpointId = await getEndpointIdFromPackageJson(resolvedPath, resolvedOptions); - // Read from .env.local to get the TRIGGER_API_KEY and TRIGGER_API_URL + // Read from env file to get the TRIGGER_API_KEY and TRIGGER_API_URL const apiDetails = await getTriggerApiDetails(resolvedPath, envFile); if (!apiDetails) { @@ -181,25 +182,18 @@ export async function devCommand(path: string, anyOptions: any) { } }; - // Watch for changes to .ts files and refresh endpoints + // Watch for changes to files and refresh endpoints const watcher = chokidar.watch( - [ - `${resolvedPath}/**/*.ts`, - `${resolvedPath}/**/*.tsx`, - `${resolvedPath}/**/*.js`, - `${resolvedPath}/**/*.jsx`, - `${resolvedPath}/**/*.json`, - `${resolvedPath}/pnpm-lock.yaml`, - ], + (framework?.watchFilePaths ?? standardWatchFilePaths).map((path) => `${resolvedPath}/${path}`), { - ignored: /(node_modules|\.next)/, + ignored: framework?.watchIgnoreRegex ?? standardWatchIgnoreRegex, //don't trigger a watch when it collects the paths ignoreInitial: true, } ); watcher.on("all", (_event, _path) => { - // console.log(_event, _path); + console.log(_event, _path); throttle(refresh, throttleTimeMs); }); diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts index 4d6cef47ba8..960791d6ec0 100644 --- a/packages/cli/src/frameworks/index.ts +++ b/packages/cli/src/frameworks/index.ts @@ -10,17 +10,41 @@ export type ProjectInstallOptions = { }; export interface Framework { + /** A unique id for the framework */ id: string; + + /** Display to the user in messages */ name: string; - defaultHostnames: string[]; - defaultPorts: number[]; + + /** Is this folder a project using this framework? */ isMatch(path: string, packageManager: PackageManager): Promise; + + /** List of packages to install */ dependencies(): Promise; + + /** Priority list of env filenames, e.g. ".env" */ possibleEnvFilenames(): string[]; + + /** Install the required files */ install(path: string, options: ProjectInstallOptions): Promise; + + /** You can check for middleware, add extra instructions, etc */ postInstall(path: string, options: ProjectInstallOptions): Promise; + + /** Used by the dev command, if a hostname isn't passed in */ + defaultHostnames: string[]; + + /** Used by the dev command, if a port isn't passed in */ + defaultPorts: number[]; + + /** These filenames are watched for changes with the dev command, can use globs. */ + watchFilePaths: string[]; + + /** These folders are ignored when watching for changes with the dev command */ + watchIgnoreRegex: RegExp; } +/** The order of these matters. The first one that matches the folder will be used, so stricter ones should be first. */ const frameworks: Framework[] = [new NextJs(), new Remix()]; export const getFramework = async ( diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts index afc5f350cf2..1ec073961d5 100644 --- a/packages/cli/src/frameworks/nextjs/index.ts +++ b/packages/cli/src/frameworks/nextjs/index.ts @@ -10,12 +10,11 @@ import { logger } from "../../utils/logger"; import { getPathAlias } from "../../utils/pathAlias"; import { readPackageJson } from "../../utils/readPackageJson"; import { detectMiddlewareUsage } from "./middleware"; +import { standardWatchFilePaths } from "../watchConfig"; export class NextJs implements Framework { id = "nextjs"; name = "Next.js"; - defaultHostnames = ["localhost"]; - defaultPorts = [3000, 3001, 3002]; async isMatch(path: string, packageManager: PackageManager): Promise { const hasNextConfigFile = await detectNextConfigFile(path); @@ -68,6 +67,11 @@ export class NextJs implements Framework { ): Promise { await detectMiddlewareUsage(path); } + + defaultHostnames = ["localhost"]; + defaultPorts = [3000, 3001, 3002]; + watchFilePaths = standardWatchFilePaths; + watchIgnoreRegex = /(node_modules|\.next)/; } async function detectNextConfigFile(path: string): Promise { diff --git a/packages/cli/src/frameworks/remix/index.ts b/packages/cli/src/frameworks/remix/index.ts index 77957294da7..0b9a04e327e 100644 --- a/packages/cli/src/frameworks/remix/index.ts +++ b/packages/cli/src/frameworks/remix/index.ts @@ -8,12 +8,11 @@ import { createFileFromTemplate } from "../../utils/createFileFromTemplate"; import { templatesPath } from "../../paths"; import { logger } from "../../utils/logger"; import { readPackageJson } from "../../utils/readPackageJson"; +import { standardWatchFilePaths } from "../watchConfig"; export class Remix implements Framework { id = "remix"; name = "Remix"; - defaultHostnames = ["localhost"]; - defaultPorts = [3000, 8788, 3333]; async isMatch(path: string, packageManager: PackageManager): Promise { //check for remix.config.js @@ -103,4 +102,9 @@ export class Remix implements Framework { } async postInstall(path: string, options: ProjectInstallOptions): Promise {} + + defaultHostnames = ["localhost"]; + defaultPorts = [3000, 8788, 3333]; + watchFilePaths = standardWatchFilePaths; + watchIgnoreRegex = /(node_modules|build)/; } diff --git a/packages/cli/src/frameworks/watchConfig.ts b/packages/cli/src/frameworks/watchConfig.ts new file mode 100644 index 00000000000..51524fb0b71 --- /dev/null +++ b/packages/cli/src/frameworks/watchConfig.ts @@ -0,0 +1,10 @@ +export const standardWatchFilePaths = [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "**/*.json", + "pnpm-lock.yaml", +]; + +export const standardWatchIgnoreRegex = /(node_modules)/; From 56efff028f54d5117466c41fec15110f4fce0017 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 18 Sep 2023 18:15:35 +0100 Subject: [PATCH 46/53] Define the watch variables above, so we can easily log them for debugging --- packages/cli/src/commands/dev.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 13fdf08aeae..9d0e1b0a40f 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -183,14 +183,15 @@ export async function devCommand(path: string, anyOptions: any) { }; // Watch for changes to files and refresh endpoints - const watcher = chokidar.watch( - (framework?.watchFilePaths ?? standardWatchFilePaths).map((path) => `${resolvedPath}/${path}`), - { - ignored: framework?.watchIgnoreRegex ?? standardWatchIgnoreRegex, - //don't trigger a watch when it collects the paths - ignoreInitial: true, - } + const watchPaths = (framework?.watchFilePaths ?? standardWatchFilePaths).map( + (path) => `${resolvedPath}/${path}` ); + const ignored = framework?.watchIgnoreRegex ?? standardWatchIgnoreRegex; + const watcher = chokidar.watch(watchPaths, { + ignored, + //don't trigger a watch when it collects the paths + ignoreInitial: true, + }); watcher.on("all", (_event, _path) => { console.log(_event, _path); From f3d5a9410123fdf07cde53ee859d2c82b594286c Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 13:07:44 +0100 Subject: [PATCH 47/53] =?UTF-8?q?Don=E2=80=99t=20wait=20for=20outdated=20p?= =?UTF-8?q?ackage=20checking=20when=20running=20the=20dev=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/commands/dev.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 9d0e1b0a40f..1984bb15fca 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -53,7 +53,9 @@ export async function devCommand(path: string, anyOptions: any) { const options = result.data; const resolvedPath = resolvePath(path); - await checkForOutdatedPackages(resolvedPath); + + //check for outdated packages, don't await this + checkForOutdatedPackages(resolvedPath); // Read from package.json to get the endpointId const endpointId = await getEndpointIdFromPackageJson(resolvedPath, options); From 3683601eca2efe968c6c9fbeaef3011b724c6c54 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 20 Sep 2023 18:28:22 -0700 Subject: [PATCH 48/53] Improved the Remix manual setup guide --- docs/_snippets/manual-setup-remix.mdx | 46 ++++++++------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/docs/_snippets/manual-setup-remix.mdx b/docs/_snippets/manual-setup-remix.mdx index cb89f338f60..d6f7d20e52e 100644 --- a/docs/_snippets/manual-setup-remix.mdx +++ b/docs/_snippets/manual-setup-remix.mdx @@ -32,50 +32,30 @@ Create a `.env` file at the root of your project and include your Trigger API ke ```bash TRIGGER_API_KEY=ENTER_YOUR_DEVELOPMENT_API_KEY_HERE -TRIGGER_API_URL=https://cloud.trigger.dev +TRIGGER_API_URL=https://api.trigger.dev # this is only necessary if you are self-hosting ``` Replace `ENTER_YOUR_DEVELOPMENT_API_KEY_HERE` with the actual API key obtained from the previous step. ## Configuring the Trigger Client -To set up the Trigger Client for your project, follow these steps: +Create a file at `/app/trigger.ts`, where `` represents the root directory of your project. -1. **Create Configuration File:** +Next, add the following code to the file which creates and exports a new `TriggerClient`: - In your project directory, create a configuration file named `trigger.ts` or `trigger.js`, depending on whether your project uses TypeScript (`.ts`) or JavaScript (`.js`). +```typescript app/trigger.(ts/js) +// trigger.ts (for TypeScript) or trigger.js (for JavaScript) -2. **Choose Directory:** +import { TriggerClient } from "@trigger.dev/sdk"; - Create the configuration file inside the **app** directory of your project. - -3. **Add Configuration Code:** - - Open the configuration file you created and add the following code: - - ```typescript app/trigger.(ts/js) - // trigger.ts (for TypeScript) or trigger.js (for JavaScript) - - import { TriggerClient } from "@trigger.dev/sdk"; - - export const client = new TriggerClient({ - id: "my-app", - apiKey: process.env.TRIGGER_API_KEY, - apiUrl: process.env.TRIGGER_API_URL, - }); - ``` - - Replace **"my-app"** with an appropriate identifier for your project. The **apiKey** and **apiUrl** are obtained from the environment variables you set earlier. - -4. **Example Directory Structure :** +export const client = new TriggerClient({ + id: "my-app", + apiKey: process.env.TRIGGER_API_KEY, + apiUrl: process.env.TRIGGER_API_URL, +}); +``` - ``` - project-root/ - β”œβ”€β”€ app/ - β”œβ”€β”€ routes/ - β”œβ”€β”€ trigger.ts - β”œβ”€β”€ other files... - ``` +Replace **"my-app"** with an appropriate identifier for your project. ## Creating the API Route From 134967e2d004dd73c6dcaff86ad76fb4bab71824 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 21 Sep 2023 08:22:27 -0700 Subject: [PATCH 49/53] Rewriting docs for quickstart --- docs/_snippets/quickstart-setup-steps.mdx | 111 ++++++++++++++++++++++ docs/documentation/quickstarts/nextjs.mdx | 106 +-------------------- docs/documentation/quickstarts/remix.mdx | 13 ++- 3 files changed, 128 insertions(+), 102 deletions(-) create mode 100644 docs/_snippets/quickstart-setup-steps.mdx diff --git a/docs/_snippets/quickstart-setup-steps.mdx b/docs/_snippets/quickstart-setup-steps.mdx new file mode 100644 index 00000000000..9c4ed9e3138 --- /dev/null +++ b/docs/_snippets/quickstart-setup-steps.mdx @@ -0,0 +1,111 @@ + + +You can either: + +- Use the [Trigger.dev Cloud](https://cloud.trigger.dev). +- Or [self-host](/documentation/guides/self-hosting) the service. + + + + + +Once you've created an account, follow the steps in the app to: + +1. Complete your account details. +2. Create your first Organization and Project. + + + + + +1. Go to the "Environments & API Keys" page in your project. + ![Go to the Environments & API Keys page ](/images/environments-link.png) + +2. Copy the `DEV` **SERVER** API key. + ![API Keys](/images/api-keys.png) + + + + + +The easiest way to get started it to use the CLI. It will add Trigger.dev to your existing project, setup a route and give you an example file. + +In a terminal window run: + + + +```bash npm +npx @trigger.dev/cli@latest init +``` + +```bash pnpm +pnpm dlx @trigger.dev/cli@latest init +``` + +```bash yarn +yarn dlx @trigger.dev/cli@latest init +``` + + + +It will ask you a couple of questions + +1. Are you using the [Trigger.dev Cloud](https://cloud.trigger.dev) or [self-hosting](/documentation/guides/self-hosting)? +2. Enter your development API key. Enter the key you copied earlier. + + + + + +Make sure your site is running locally, we will connect to it to register your Jobs. + +You must leave this running for the rest of the steps. + + + +```bash npm +npm run dev +``` + +```bash pnpm +pnpm run dev +``` + +```bash yarn +yarn run dev +``` + + + + + + + +The CLI `dev` command allows the Trigger.dev service to send messages to your site. This is required for registering Jobs, triggering them and running tasks. To achieve this it creates a tunnel (using [ngrok](https://ngrok.com/)) so Trigger.dev can send messages to your machine. + +You should leave the `dev` command running when you're developing. + +In a **new terminal window or tab** run: + + + +```bash npm +npx @trigger.dev/cli@latest dev +``` + +```bash pnpm +pnpm dlx @trigger.dev/cli@latest dev +``` + +```bash yarn +yarn dlx @trigger.dev/cli@latest dev +``` + + +
+ + You can optionally pass the port if you're not running on the default port by adding + `--port 3001` to the end. + + +
diff --git a/docs/documentation/quickstarts/nextjs.mdx b/docs/documentation/quickstarts/nextjs.mdx index 2cefe446266..63e00b1610d 100644 --- a/docs/documentation/quickstarts/nextjs.mdx +++ b/docs/documentation/quickstarts/nextjs.mdx @@ -17,106 +17,8 @@ Trigger.dev works with either the Pages or App Router configuration. -## Create a Trigger.dev account - -You can either: - -- Use the [Trigger.dev Cloud](https://cloud.trigger.dev). -- Or [self-host](/documentation/guides/self-hosting) the service. - -### Create your first project - -Once you've created an account, follow the steps in the app to: - -1. Complete your account details. -2. Create your first Organization and Project. - -### Getting an API key - -1. Go to the "Environments & API Keys" page in your project. - ![Go to the Environments & API Keys page ](/images/environments-link.png) - -2. Copy the `DEV` **SERVER** API key. - ![API Keys](/images/api-keys.png) - -## Run the CLI `init` command - -The easiest way to get started it to use the CLI. It will add Trigger.dev to your existing Next.js project, setup a route and give you an example file. - -In a terminal window run: - - - -```bash npm -npx @trigger.dev/cli@latest init -``` - -```bash pnpm -pnpm dlx @trigger.dev/cli@latest init -``` - -```bash yarn -yarn dlx @trigger.dev/cli@latest init -``` - - - -It will ask you a few questions - -1. Are you using the [Trigger.dev Cloud](https://cloud.trigger.dev) or [self-hosting](/documentation/guides/self-hosting)? -2. Enter your development API key. Enter the key you copied earlier. -3. Enter a unique ID for your endpoint (you can just use the default by hitting enter) - -## Run your Next.js site - -Make sure your Next.js site is running locally, we will connect to it to register your Jobs. - -You must leave this running for the rest of the steps. - - - -```bash npm -npm run dev -``` - -```bash pnpm -pnpm run dev -``` - -```bash yarn -yarn run dev -``` - - - -## Run the CLI `dev` command - -The CLI `dev` command allows the Trigger.dev service to send messages to your Next.js site. This is required for registering Jobs, triggering them and running tasks. To achieve this it creates a tunnel (using [ngrok](https://ngrok.com/)) so Trigger.dev can send messages to your machine. - -You should leave the `dev` command running when you're developing. - -In a **new terminal window or tab** run: - - - -```bash npm -npx @trigger.dev/cli@latest dev -``` - -```bash pnpm -pnpm dlx @trigger.dev/cli@latest dev -``` - -```bash yarn -yarn dlx @trigger.dev/cli@latest dev -``` - - -
- - You can optionally pass the port if you're not running on 3000 by adding - `--port 3001` to the end - + + @@ -152,7 +54,7 @@ yarn add concurrently --dev "dev": "concurrently --kill-others npm:dev:*", "dev:next": "next dev", "dev:trigger": "npx @trigger.dev/cli dev", - ... + //... } ... ``` @@ -210,6 +112,8 @@ This Job doesn't have a payload schema (meaning it takes an empty object), so yo Congratulations, you should get redirected so you can see your first Run! + + ## What's next? diff --git a/docs/documentation/quickstarts/remix.mdx b/docs/documentation/quickstarts/remix.mdx index a3a2bb174f8..6dc136af428 100644 --- a/docs/documentation/quickstarts/remix.mdx +++ b/docs/documentation/quickstarts/remix.mdx @@ -4,4 +4,15 @@ sidebarTitle: "Remix" description: "Start creating Jobs in 5 minutes in your Remix project." --- - +This quick start guide will get you up and running with Trigger.dev. + + +No problem, create a blank project by running the `create-remix` command in your terminal then continue with this quickstart guide as normal: + +```bash +npx create-remix@latest +``` + +Trigger.dev works with Remix v1 and v2. + + From a494331d0dad39fae2d43fbdeb01fae8b6b6322d Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 21 Sep 2023 09:25:04 -0700 Subject: [PATCH 50/53] Updated the Next.js quickstart --- docs/_snippets/quickstart-example-job.mdx | 26 +++++++ .../_snippets/quickstart-running-your-job.mdx | 18 +++++ docs/_snippets/quickstart-setup-steps.mdx | 31 -------- docs/documentation/quickstarts/nextjs.mdx | 75 +++++++++---------- 4 files changed, 80 insertions(+), 70 deletions(-) create mode 100644 docs/_snippets/quickstart-example-job.mdx create mode 100644 docs/_snippets/quickstart-running-your-job.mdx diff --git a/docs/_snippets/quickstart-example-job.mdx b/docs/_snippets/quickstart-example-job.mdx new file mode 100644 index 00000000000..6bf8b662f0f --- /dev/null +++ b/docs/_snippets/quickstart-example-job.mdx @@ -0,0 +1,26 @@ +```typescript +// Your first job +// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline +client.defineJob({ + // This is the unique identifier for your Job, it must be unique across all Jobs in your project + id: "example-job", + name: "Example Job: a joke with a delay", + version: "0.0.1", + // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + // This logs a message to the console + await io.logger.info("πŸ§ͺ Example Job: a joke with a delay"); + await io.logger.info("How do you comfort a JavaScript bug?"); + // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year + await io.wait("Wait 5 seconds for the punchline...", 5); + await io.logger.info("You console it! 🀦"); + await io.logger.info( + "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" + ); + // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job + }, +}); +``` diff --git a/docs/_snippets/quickstart-running-your-job.mdx b/docs/_snippets/quickstart-running-your-job.mdx new file mode 100644 index 00000000000..bd18bb531fc --- /dev/null +++ b/docs/_snippets/quickstart-running-your-job.mdx @@ -0,0 +1,18 @@ + + +There are two way to trigger this Job. + +1. Use the "Test" functionality in the dashboard. +2. Use the Trigger.dev API (either via our SDK or a web request) + +#### "Testing" from the dashboard + +Click into the Job and then open the "Test" tab. You should see this page: + +![Test Job](/images/test-job.png) + +This Job doesn't have a payload schema (meaning it takes an empty object), so you can simple click the "Run test" button. + +**Congratulations, you should get redirected so you can see your first Run!** + + diff --git a/docs/_snippets/quickstart-setup-steps.mdx b/docs/_snippets/quickstart-setup-steps.mdx index 9c4ed9e3138..fbcf9e9c32f 100644 --- a/docs/_snippets/quickstart-setup-steps.mdx +++ b/docs/_snippets/quickstart-setup-steps.mdx @@ -78,34 +78,3 @@ yarn run dev - - - -The CLI `dev` command allows the Trigger.dev service to send messages to your site. This is required for registering Jobs, triggering them and running tasks. To achieve this it creates a tunnel (using [ngrok](https://ngrok.com/)) so Trigger.dev can send messages to your machine. - -You should leave the `dev` command running when you're developing. - -In a **new terminal window or tab** run: - - - -```bash npm -npx @trigger.dev/cli@latest dev -``` - -```bash pnpm -pnpm dlx @trigger.dev/cli@latest dev -``` - -```bash yarn -yarn dlx @trigger.dev/cli@latest dev -``` - - -
- - You can optionally pass the port if you're not running on the default port by adding - `--port 3001` to the end. - - -
diff --git a/docs/documentation/quickstarts/nextjs.mdx b/docs/documentation/quickstarts/nextjs.mdx index 63e00b1610d..ed5a10cf94d 100644 --- a/docs/documentation/quickstarts/nextjs.mdx +++ b/docs/documentation/quickstarts/nextjs.mdx @@ -20,6 +20,35 @@ Trigger.dev works with either the Pages or App Router configuration. + + +The CLI `dev` command allows the Trigger.dev service to send messages to your site. This is required for registering Jobs, triggering them and running tasks. To achieve this it creates a tunnel (using [ngrok](https://ngrok.com/)) so Trigger.dev can send messages to your machine. + +You should leave the `dev` command running when you're developing. + +In a **new terminal window or tab** run: + + + +```bash npm +npx @trigger.dev/cli@latest dev +``` + +```bash pnpm +pnpm dlx @trigger.dev/cli@latest dev +``` + +```bash yarn +yarn dlx @trigger.dev/cli@latest dev +``` + + +
+ + You can optionally pass the port if you're not running on the default port by adding + `--port 3001` to the end. + + Instructions of how to resolve any issues due to middleware [here](https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware) @@ -62,55 +91,23 @@ yarn add concurrently --dev -## Your first job +
+ + -The CLI init command created a simple Job for you. There will be a new file either `api/trigger/route.ts` or `pages/api/trigger.ts`. +The CLI init command created a simple Job for you. There will be a new file either `src/jobs/examples.(ts/js)` or `jobs/examples.(ts/js)`. In there is this Job: -```typescript -//Job definition – uses the client -client.defineJob({ - // 1. Metadata - id: "example-job", - name: "Example Job", - version: "0.0.1", - // 2. Trigger - trigger: eventTrigger({ - name: "example.event", - }), - // 3. Run function - run: async (payload, io, ctx) => { - // do something - await io.logger.info("Hello world!", { payload }); - - return { - message: "Hello world!", - }; - }, -}); -``` + If you navigate to your Trigger.dev project you will see this Job in the "Jobs" section: ![Your first Job](/images/first-job.png) -## Triggering the Job - -There are two way to trigger this Job. - -1. Use the "Test" functionality in the dashboard. -2. Use the Trigger.dev API (either via our SDK or a web request) - -### "Testing" from the dashboard - -Click into the Job and then open the "Test" tab. You should see this page: - -![Test Job](/images/test-job.png) - -This Job doesn't have a payload schema (meaning it takes an empty object), so you can simple click the "Run test" button. + -Congratulations, you should get redirected so you can see your first Run! +
From c0b55573d69f9a6069afe9998ac48b0e5b03382f Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 21 Sep 2023 10:00:49 -0700 Subject: [PATCH 51/53] Remix quick start --- docs/_snippets/quickstart-cli-dev.mdx | 26 +++++++++ docs/_snippets/quickstart-whats-next.mdx | 20 +++++++ docs/documentation/quickstarts/nextjs.mdx | 48 +--------------- docs/documentation/quickstarts/remix.mdx | 68 +++++++++++++++++++++++ 4 files changed, 116 insertions(+), 46 deletions(-) create mode 100644 docs/_snippets/quickstart-cli-dev.mdx create mode 100644 docs/_snippets/quickstart-whats-next.mdx diff --git a/docs/_snippets/quickstart-cli-dev.mdx b/docs/_snippets/quickstart-cli-dev.mdx new file mode 100644 index 00000000000..866a8ee4a92 --- /dev/null +++ b/docs/_snippets/quickstart-cli-dev.mdx @@ -0,0 +1,26 @@ +The CLI `dev` command allows the Trigger.dev service to send messages to your site. This is required for registering Jobs, triggering them and running tasks. To achieve this it creates a tunnel (using [ngrok](https://ngrok.com/)) so Trigger.dev can send messages to your machine. + +You should leave the `dev` command running when you're developing. + +In a **new terminal window or tab** run: + + + +```bash npm +npx @trigger.dev/cli@latest dev +``` + +```bash pnpm +pnpm dlx @trigger.dev/cli@latest dev +``` + +```bash yarn +yarn dlx @trigger.dev/cli@latest dev +``` + + +
+ + You can optionally pass the port if you're not running on the default port by adding + `--port 3001` to the end. + diff --git a/docs/_snippets/quickstart-whats-next.mdx b/docs/_snippets/quickstart-whats-next.mdx new file mode 100644 index 00000000000..5d815504e5e --- /dev/null +++ b/docs/_snippets/quickstart-whats-next.mdx @@ -0,0 +1,20 @@ +## What's next? + + + + A Guide for how to create your first real Job + + + Learn more about how Trigger.dev works and how it can help you. + + + One of the quickest ways to learn how Trigger.dev works is to view some example Jobs. + + + Struggling getting setup or have a question? We're here to help. + + diff --git a/docs/documentation/quickstarts/nextjs.mdx b/docs/documentation/quickstarts/nextjs.mdx index ed5a10cf94d..3faade9622a 100644 --- a/docs/documentation/quickstarts/nextjs.mdx +++ b/docs/documentation/quickstarts/nextjs.mdx @@ -22,32 +22,7 @@ Trigger.dev works with either the Pages or App Router configuration. -The CLI `dev` command allows the Trigger.dev service to send messages to your site. This is required for registering Jobs, triggering them and running tasks. To achieve this it creates a tunnel (using [ngrok](https://ngrok.com/)) so Trigger.dev can send messages to your machine. - -You should leave the `dev` command running when you're developing. - -In a **new terminal window or tab** run: - - - -```bash npm -npx @trigger.dev/cli@latest dev -``` - -```bash pnpm -pnpm dlx @trigger.dev/cli@latest dev -``` - -```bash yarn -yarn dlx @trigger.dev/cli@latest dev -``` - - -
- - You can optionally pass the port if you're not running on the default port by adding - `--port 3001` to the end. - + @@ -111,23 +86,4 @@ If you navigate to your Trigger.dev project you will see this Job in the "Jobs" -## What's next? - - - - A Guide for how to create your first real Job - - - Learn more about how Trigger.dev works and how it can help you. - - - One of the quickest ways to learn how Trigger.dev works is to view some example Jobs. - - - Struggling getting setup or have a question? We're here to help. - - + diff --git a/docs/documentation/quickstarts/remix.mdx b/docs/documentation/quickstarts/remix.mdx index 6dc136af428..2d8cc216047 100644 --- a/docs/documentation/quickstarts/remix.mdx +++ b/docs/documentation/quickstarts/remix.mdx @@ -16,3 +16,71 @@ npx create-remix@latest Trigger.dev works with Remix v1 and v2. + + + + + + + + + + +You can modify your `package.json` to run both the Remix server and the CLI `dev` command together. + +1. Install the `concurrently` package: + + + +```bash npm +npm install concurrently --save-dev +``` + +```bash pnpm +pnpm install concurrently --save-dev +``` + +```bash yarn +yarn add concurrently --dev +``` + + + +2. Modify your `package.json` file's `dev` script. + +```json package.json +//... +"scripts": { + "dev": "concurrently --kill-others npm:dev:*", + //your normal remix dev command would go here + "dev:remix": "remix dev", + "dev:trigger": "npx @trigger.dev/cli dev", + //... +} +//... +``` + + + + + + + + +The CLI init command created a simple Job for you. There will be a new file either `app/jobs/example.server.(ts/js)`. + +In there is this Job: + + + +If you navigate to your Trigger.dev project you will see this Job in the "Jobs" section: + +![Your first Job](/images/first-job.png) + + + + + + + + From 8c9963c210ccd6e4be016fb59034bf158f689dcf Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 21 Sep 2023 10:03:17 -0700 Subject: [PATCH 52/53] Added a changeset --- .changeset/four-apes-sleep.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/four-apes-sleep.md diff --git a/.changeset/four-apes-sleep.md b/.changeset/four-apes-sleep.md new file mode 100644 index 00000000000..3e88016b006 --- /dev/null +++ b/.changeset/four-apes-sleep.md @@ -0,0 +1,6 @@ +--- +"@trigger.dev/remix": patch +"@trigger.dev/cli": patch +--- + +CLI now supports multiple frameworks (starting with Next.js and Remix) From 2595fc3ef225993752b7e226ded5bc7a4197d38c Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 21 Sep 2023 10:15:30 -0700 Subject: [PATCH 53/53] Improved the Next.js manual setup --- docs/documentation/guides/manual/nextjs.mdx | 98 ++++++++------------- 1 file changed, 36 insertions(+), 62 deletions(-) diff --git a/docs/documentation/guides/manual/nextjs.mdx b/docs/documentation/guides/manual/nextjs.mdx index 56285d2f8eb..57d549a29af 100644 --- a/docs/documentation/guides/manual/nextjs.mdx +++ b/docs/documentation/guides/manual/nextjs.mdx @@ -22,19 +22,15 @@ To begin, install the necessary packages in your Next.js project directory. You ```bash npm - npm i @trigger.dev/sdk @trigger-dev/nextjs - ``` ```bash pnpm pnpm install @trigger.dev/sdk @trigger-dev/nextjs - ``` ```bash yarn yarn add @trigger.dev/sdk @trigger-dev/nextjs - ``` @@ -58,7 +54,7 @@ Create a `.env.local` file at the root of your project and include your Trigger ```bash TRIGGER_API_KEY=ENTER_YOUR_DEVELOPMENT_API_KEY_HERE -TRIGGER_API_URL=https://cloud.trigger.dev +TRIGGER_API_URL=https://api.trigger.dev # this is only necessary if you are self-hosting ``` @@ -66,59 +62,23 @@ Replace `ENTER_YOUR_DEVELOPMENT_API_KEY_HERE` with the actual API key obtained f ## Configuring the Trigger Client -To set up the Trigger Client for your project, follow these steps: - -1. **Create Configuration File:** - - In your project directory, create a configuration file named `trigger.ts` or `trigger.js`, depending on whether your project uses TypeScript (`.ts`) or JavaScript (`.js`). - -2. **Choose Directory:** - - Depending on your project structure, choose the appropriate directory for the configuration file. If your project uses a `src` directory, create the file within it. Otherwise, create it directly in the project root. - -3. **Add Configuration Code:** +Create a file at `/src/trigger.ts` or `/trigger.ts` depending on whether you're using the `src` directory or not. `` represents the root directory of your project. - Open the configuration file you created and add the following code: +Next, add the following code to the file which creates and exports a new `TriggerClient`: - ```typescript - // trigger.ts (for TypeScript) or trigger.js (for JavaScript) +```typescript src/trigger.(ts/js) +// trigger.ts (for TypeScript) or trigger.js (for JavaScript) - import { TriggerClient } from "@trigger.dev/sdk"; +import { TriggerClient } from "@trigger.dev/sdk"; - export const client = new TriggerClient({ - id: "my-app", - apiKey: process.env.TRIGGER_API_KEY, - apiUrl: process.env.TRIGGER_API_URL, - }); - ``` - - Replace **"my-app"** with an appropriate identifier for your project. The **apiKey** and **apiUrl** are obtained from the environment variables you set earlier. - -4. **File Location:** - - Depending on your project structure, save the configuration file in the appropriate location: - - - If your project uses a **src** directory, save the file within the **src** directory. - - If your project does not use a **src** directory, save the file in the project root. - - **Example Directory Structure with src:** - - ``` - project-root/ - β”œβ”€β”€ src/ - β”œβ”€β”€ trigger.ts - β”œβ”€β”€ other files... - ``` - - **Example Directory Structure without src:** - - ``` - project-root/ - β”œβ”€β”€ trigger.ts - β”œβ”€β”€ other files... - ``` +export const client = new TriggerClient({ + id: "my-app", + apiKey: process.env.TRIGGER_API_KEY, + apiUrl: process.env.TRIGGER_API_URL, +}); +``` -By following these steps, you'll configure the Trigger Client to work with your project, regardless of whether you have a separate **src** directory and whether you're using TypeScript or JavaScript files. +Replace **"my-app"** with an appropriate identifier for your project. ## Creating the API Route @@ -238,18 +198,31 @@ Your `package.json` file might look something like this: Replace **"my-app"** with the appropriate identifier you used during the step for creating the Trigger Client. -## Next Steps +## Running -Start your Next.js project locally, and then execute the `dev` CLI command to run Trigger.dev locally. You should run this command every time you want to use Trigger.dev locally. +### Run your Next.js app -![Your first Job](/images/cli-dev.gif) +Run your Next.js app locally, like you normally would. For example: - - Make sure your Next.js site is running locally before continuing. You must also leave this `dev` - terminal command running while you develop. - + -In a **new terminal window or tab** run: +```bash npm +npm run dev +``` + +```bash pnpm +pnpm run dev +``` + +```bash yarn +yarn run dev +``` + + + +### Run the CLI 'dev' command + +In a **_separate terminal window or tab_** run: @@ -271,9 +244,10 @@ yarn dlx @trigger.dev/cli@latest dev You can optionally pass the port if you're not running on 3000 by adding `--port 3001` to the end + You can optionally pass the hostname if you're not running on localhost by adding - `--hostname `. Example, in case your Next.js is running on 0.0.0.0: `--hostname 0.0.0.0`. + `--hostname `. Example, in case your Remix is running on 0.0.0.0: `--hostname 0.0.0.0`.