Improved the CLI prompts

This commit is contained in:
Eric Allam
2023-06-23 14:41:58 +01:00
parent d0ad2bd38b
commit 2e1624235c
7 changed files with 148 additions and 34 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/init": patch
---
Improved the CLI prompts
+1
View File
@@ -21,6 +21,7 @@
"dirty-needles-kneel",
"four-ligers-perform",
"great-meals-listen",
"heavy-mails-explain",
"itchy-rockets-provide",
"modern-emus-suffer",
"quick-stingrays-own",
+6
View File
@@ -1,5 +1,11 @@
# create-trigger
## 0.2.1-next.3
### Patch Changes
- Improved the CLI prompts
## 0.2.1-next.2
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/init",
"version": "0.2.1-next.2",
"version": "0.2.1-next.3",
"description": "The CLI to easily initialize Trigger.dev in your Next.js project",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+96 -9
View File
@@ -1,6 +1,6 @@
import { Command } from "commander";
import inquirer from "inquirer";
import { COMMAND_NAME } from "../consts.js";
import { COMMAND_NAME, DEFAULT_TRIGGER_URL } from "../consts.js";
import { getVersion } from "../utils/getVersion.js";
import { logger } from "../utils/logger.js";
@@ -19,7 +19,7 @@ export interface CliResults {
const defaultOptions: CliResults = {
flags: {
projectPath: ".",
triggerUrl: "https://cloud.trigger.dev",
triggerUrl: DEFAULT_TRIGGER_URL,
endpointUrl: "http://localhost:3000",
endpointSlug: "my-nextjs-project",
},
@@ -48,13 +48,11 @@ export const parseCliOptions = async () => {
)
.option(
"-u, --endpoint-url <endpoint-url>",
"The URL of your local Next.js project. (e.g. http://localhost:3000). NOTE: Must be a publicly accessible URL if you are using a deployed Trigger.dev instance",
"http://localhost:3000"
"The URL of your local Next.js project. (e.g. http://localhost:3000). NOTE: Must be a publicly accessible URL if you are using a deployed Trigger.dev instance"
)
.option(
"-t, --trigger-url <trigger-url>",
"The URL of the Trigger.dev instance to use. (e.g. https://cloud.trigger.dev)",
"https://cloud.trigger.dev"
"The URL of the Trigger.dev instance to use. (e.g. https://cloud.trigger.dev)"
)
.version(getVersion(), "-v, --version", "Display the version number")
.parse(process.argv);
@@ -66,13 +64,23 @@ export const parseCliOptions = async () => {
export const runCliPrompts = async (cliResults: CliResults) => {
try {
if (!cliResults.flags.triggerUrl) {
cliResults.flags.triggerUrl = await promptTriggerUrl();
}
if (!cliResults.flags.apiKey) {
cliResults.flags.apiKey = await promptApiKey();
cliResults.flags.apiKey = await promptApiKey(cliResults.flags.triggerUrl);
}
if (!cliResults.flags.endpointSlug) {
cliResults.flags.endpointSlug = await promptEndpointSlug();
}
if (!cliResults.flags.endpointUrl) {
cliResults.flags.endpointUrl = await promptEndpointUrl(
cliResults.flags.triggerUrl
);
}
} catch (err) {
// If the user is not calling the command from an interactive terminal, inquirer will throw an error with isTTYError = true
// If this happens, we catch the error, tell the user what has happened, and then continue to run the program with a default trigger project
@@ -104,12 +112,54 @@ export const runCliPrompts = async (cliResults: CliResults) => {
return cliResults;
};
const promptApiKey = async (): Promise<string> => {
const promptTriggerUrl = async (): Promise<string> => {
const { instanceType } = await inquirer.prompt<{
instanceType: "cloud" | "self-hosted";
}>([
{
type: "list",
name: "instanceType",
message: "Are you using the Trigger.dev cloud or self-hosted?",
choices: [
{
name: "Trigger.dev Cloud",
value: "cloud",
default: true,
},
{
name: "Self hosted",
value: "self-hosted",
},
],
},
]);
if (instanceType === "cloud") {
return DEFAULT_TRIGGER_URL;
}
const { triggerUrl } = await inquirer.prompt<{ triggerUrl: string }>({
type: "input",
name: "triggerUrl",
message: "Enter the URL of your self-hosted Trigger.dev instance",
validate: (input) => {
if (!input) {
return "Please enter the URL of your self-hosted Trigger.dev instance";
}
return true;
},
});
return triggerUrl;
};
const promptApiKey = async (instanceUrl: string): Promise<string> => {
// First prompt if they want to enter their API key now, and if they say Yes, then prompt for it and return it
const { apiKey } = await inquirer.prompt<{ apiKey: string }>({
type: "input",
name: "apiKey",
message: "Enter your development API key (required)",
message: `Enter your development API key (Find yours ➡️ ${instanceUrl})`,
validate: (input) => {
// Make sure they enter something like tr_dev_********
if (!input) {
@@ -146,6 +196,43 @@ const promptEndpointSlug = async (): Promise<string> => {
return endpointSlug;
};
const promptEndpointUrl = async (instanceUrl: string): Promise<string> => {
const { endpointUrl } = await inquirer.prompt<{
endpointUrl: string;
}>({
type: "input",
name: "endpointUrl",
message:
"What's the URL of your Next.js project? (e.g. http://localhost:3000)",
validate: (input) => {
if (!input) {
return "Please enter the URL of your Next.js project";
}
// If instanceUrl is a cloud instance, then the URL must be publicly accessible
const url = new URL(input);
const triggerUrl = new URL(instanceUrl);
if (triggerUrl.hostname !== "localhost" && url.hostname === "localhost") {
return `Your Trigger.dev instance is hosted at ${triggerUrl.hostname}, so your Next.js project must also be publicly accessible. See our docs for more info: https://trigger.dev/docs/guides/tunneling`;
}
// Make sure triggerUrl and url don't use the same port if they are both localhost
if (
triggerUrl.hostname === "localhost" &&
url.hostname === "localhost" &&
triggerUrl.port === url.port
) {
return `Your Trigger.dev instance and your Next.js project are both trying to use port ${triggerUrl.port}. Please use a different port for one of them`;
}
return true;
},
});
return endpointUrl;
};
export const obfuscateApiKey = (apiKey: string) => {
const [prefix, slug, secretPart] = apiKey.split("_") as [
string,
+1 -2
View File
@@ -18,5 +18,4 @@ export const TITLE_TEXT = `
export const DEFAULT_APP_NAME = "my-triggers";
export const COMMAND_NAME = "@trigger.dev/init";
export const TEMPLATE_ORGANIZATION = "triggerdotdev";
export const TRIGGER_BASE_URL =
process.env.TRIGGER_BASE_URL ?? "https://cloud.trigger.dev";
export const DEFAULT_TRIGGER_URL = "https://cloud.trigger.dev";
+38 -22
View File
@@ -9,12 +9,22 @@ import fs from "fs/promises";
import pathModule from "path";
import { simpleGit } from "simple-git";
import { TriggerApi } from "./utils/triggerApi.js";
import { DEFAULT_TRIGGER_URL } from "./consts.js";
import ora from "ora";
const main = async () => {
renderTitle();
const cliOptions = await parseCliOptions();
if (cliOptions.flags.triggerUrl === DEFAULT_TRIGGER_URL) {
logger.info(`✨ Initializing project in Trigger.dev Cloud`);
} else {
logger.info(
`✨ Initializing project using Trigger.dev at ${cliOptions.flags.triggerUrl}`
);
}
const resolvedPath = resolvePath(cliOptions.flags.projectPath);
// Detect if are are in a Next.js project
const isNextJsProject = await detectNextJsProject(resolvedPath);
@@ -61,12 +71,7 @@ const main = async () => {
]);
// Setup environment variables
const addedEnvVars = await setupEnvironmentVariables(
resolvedPath,
cliResults
);
logger.success(`✅ Setup environment variables ${addedEnvVars.join(", ")}`);
await setupEnvironmentVariables(resolvedPath, cliResults);
const usesSrcDir = await detectUseOfSrcDir(resolvedPath);
@@ -84,6 +89,8 @@ const main = async () => {
await createTriggerAppRoute(routeDir, cliResults, usesSrcDir);
}
await waitForProjectToBuild();
const api = new TriggerApi(apiKey, cliResults.flags.triggerUrl);
const endpoint = await api.createEndpoint({
@@ -305,14 +312,7 @@ export const { POST, dynamic } = createAppRoute(client, {
);
}
type EnvironmentVariable = "TRIGGER_API_KEY" | "TRIGGER_API_URL" | "VERCEL_URL";
async function setupEnvironmentVariables(
path: string,
cliResults: CliResults
): Promise<Array<EnvironmentVariable>> {
let results: Array<EnvironmentVariable> = [];
async function setupEnvironmentVariables(path: string, cliResults: CliResults) {
const envFilePath = pathModule.join(path, ".env.local");
const envFileExists = await pathExists(envFilePath);
@@ -327,13 +327,15 @@ async function setupEnvironmentVariables(
);
await fs.writeFile(envFilePath, updatedEnvFileContent);
results.push("TRIGGER_API_KEY");
logger.success("✅ Updated TRIGGER_API_KEY in .env.local");
} else {
await fs.appendFile(
envFilePath,
`TRIGGER_API_KEY=${cliResults.flags.apiKey}\n`
);
results.push("TRIGGER_API_KEY");
logger.success("✅ Added TRIGGER_API_KEY to .env.local");
}
if (envFileContent.includes("TRIGGER_API_URL")) {
@@ -344,13 +346,15 @@ async function setupEnvironmentVariables(
);
await fs.writeFile(envFilePath, updatedEnvFileContent);
results.push("TRIGGER_API_URL");
logger.success("✅ Updated TRIGGER_API_URL in .env.local");
} else {
await fs.appendFile(
envFilePath,
`TRIGGER_API_URL=${cliResults.flags.triggerUrl}\n`
);
results.push("TRIGGER_API_URL");
logger.success("✅ Added TRIGGER_API_URL to .env.local");
}
if (!envFileContent.includes("VERCEL_URL")) {
@@ -358,7 +362,8 @@ async function setupEnvironmentVariables(
envFilePath,
`VERCEL_URL=${cliResults.flags.endpointUrl}\n`
);
results.push("VERCEL_URL");
logger.success("✅ Added VERCEL_URL to .env.local");
}
} else {
const envFileContent = `
@@ -368,10 +373,11 @@ VERCEL_URL=${cliResults.flags.endpointUrl}
`;
await fs.writeFile(envFilePath, envFileContent);
results = ["TRIGGER_API_KEY", "TRIGGER_API_URL", "VERCEL_URL"];
}
return results;
logger.success(
"✅ Created .env.local and added required environment variables"
);
}
}
async function pathExists(path: string): Promise<boolean> {
@@ -382,3 +388,13 @@ async function pathExists(path: string): Promise<boolean> {
return false;
}
}
async function waitForProjectToBuild() {
const spinner = ora("Waiting for project to build...").start();
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
spinner.stop();
}