Skip to content

Commit

Permalink
Improved the CLI prompts
Browse files Browse the repository at this point in the history
  • Loading branch information
ericallam committed Jun 23, 2023
1 parent d0ad2bd commit 2e16242
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .changeset/heavy-mails-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/init": patch
---

Improved the CLI prompts
1 change: 1 addition & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions packages/init-trigger/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# create-trigger

## 0.2.1-next.3

### Patch Changes

- Improved the CLI prompts

## 0.2.1-next.2

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/init-trigger/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
105 changes: 96 additions & 9 deletions packages/init-trigger/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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",
},
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions packages/init-trigger/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
60 changes: 38 additions & 22 deletions packages/init-trigger/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -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({
Expand Down Expand Up @@ -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);

Expand All @@ -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")) {
Expand All @@ -344,21 +346,24 @@ 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")) {
await fs.appendFile(
envFilePath,
`VERCEL_URL=${cliResults.flags.endpointUrl}\n`
);
results.push("VERCEL_URL");

logger.success("✅ Added VERCEL_URL to .env.local");
}
} else {
const envFileContent = `
Expand All @@ -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> {
Expand All @@ -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();
}

0 comments on commit 2e16242

Please sign in to comment.