Adding the @trigger.dev/init CLI package
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
---
|
||||
"@trigger.dev/integration-kit": patch
|
||||
"@trigger.dev/sdk": patch
|
||||
"@trigger.dev/github": patch
|
||||
"@trigger.dev/openai": patch
|
||||
"@trigger.dev/slack": patch
|
||||
"@trigger.dev/nextjs": patch
|
||||
"@trigger.dev/init": patch
|
||||
---
|
||||
|
||||
Creating the init CLI package
|
||||
@@ -36,6 +36,11 @@ export async function action({ request }: ActionArgs) {
|
||||
return json({ error: "Invalid request body" }, { status: 400 });
|
||||
}
|
||||
|
||||
logger.info("Creating endpoint", {
|
||||
url: request.url,
|
||||
endpoint: body.data,
|
||||
});
|
||||
|
||||
const service = new CreateEndpointService();
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
## ✨ Create Trigger - Get started writing Trigger.dev code quickly
|
||||
|
||||
Trigger.dev is an open source platform that makes it easy to create event-driven background tasks directly your codebase.
|
||||
|
||||
You can run these tasks (or "workflows" as we like to cal them) in your existing Node.js repo, but if you don't have one of those (👋 Next.js devs) or you just want to try us out without the setup, this `create-trigger` CLI will scaffold out a project for you in just a few seconds, either starting from scratch or using one of our many [templates](https://app.trigger.com/templates).
|
||||
|
||||
## 💻 Usage
|
||||
|
||||
To scaffold out a new project using `create-trigger`, run any of the following three commands and answer the prompts:
|
||||
|
||||
### npm
|
||||
|
||||
```sh
|
||||
npx create-trigger@latest
|
||||
```
|
||||
|
||||
### yarn
|
||||
|
||||
```sh
|
||||
yarn create trigger
|
||||
```
|
||||
|
||||
### pnpm
|
||||
|
||||
```sh
|
||||
pnpm create trigger@latest
|
||||
```
|
||||
|
||||
You can also specify the [template](https://app.trigger.com/templates) you want to use by passing an argument to the command, like so:
|
||||
|
||||
```sh
|
||||
npx create-trigger@latest github-stars-to-slack
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
| Option/Flag | Description |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `[template]` | The name of the template to use, e.g. basic-starter |
|
||||
| `-p, --projectName` | The name of the project, as well as the name of the directory to create. Can be a path to a directory, e.g. ~/projects/my-project |
|
||||
| `-k, --apiKey` | The development API key to use for the project. Visit https://app.trigger.dev to get yours |
|
||||
| `--noGit` | Explicitly tell the CLI to not initialize a new git repo in the project |
|
||||
| `--noInstall` | Explicitly tell the CLI to not run the package manager's install command |
|
||||
|
||||
## Folder structure
|
||||
|
||||
```
|
||||
├── LICENSE
|
||||
├── README.md
|
||||
├── package.json
|
||||
├── render.yaml
|
||||
├── .env
|
||||
├── .env.example
|
||||
├── src
|
||||
│ └── index.ts
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
### `src/index.ts`
|
||||
|
||||
All your Trigger.dev workflow code will be in here, and this is the part you can start customizing.
|
||||
|
||||
### `.env`
|
||||
|
||||
If provided, we'll save your development API Key here so running the project can connect to our servers.
|
||||
|
||||
### `render.yaml`
|
||||
|
||||
A [Render.com](https://render.com) Blueprint file that makes it easy to deploy your repo as a Background Worker.
|
||||
|
||||
### `README.md`
|
||||
|
||||
Contains useful instructions for getting started with the repo, including how to customize it, running it locally, testing it, and deploying it.
|
||||
|
||||
## Next steps
|
||||
|
||||
After you successfully scaffold out your project, take a look at the README. If you have any issues, please feel free to email us at hello@trigger.dev, or you can ask a question in our [Discord server](https://discord.gg/nkqV9xBYWy)
|
||||
@@ -1,311 +0,0 @@
|
||||
import chalk from "chalk";
|
||||
import { Command } from "commander";
|
||||
import inquirer from "inquirer";
|
||||
import terminalLink from "terminal-link";
|
||||
import {
|
||||
CREATE_TRIGGER,
|
||||
DEFAULT_APP_NAME as DEFAULT_PROJECT_NAME,
|
||||
} from "../consts.js";
|
||||
import { getUserPkgManager } from "../utils/getUserPkgManager.js";
|
||||
import { getVersion } from "../utils/getVersion.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
import { getTemplates } from "../utils/triggerApi.js";
|
||||
|
||||
export interface CliFlags {
|
||||
noGit: boolean;
|
||||
noInstall: boolean;
|
||||
noTelemetry: boolean;
|
||||
projectName: string;
|
||||
apiKey?: string;
|
||||
}
|
||||
|
||||
export interface CliResults {
|
||||
templateName: string;
|
||||
flags: CliFlags;
|
||||
}
|
||||
|
||||
const defaultOptions: CliResults = {
|
||||
templateName: "blank-starter",
|
||||
flags: {
|
||||
noGit: false,
|
||||
noInstall: false,
|
||||
noTelemetry: false,
|
||||
projectName: DEFAULT_PROJECT_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
export const runCli = async () => {
|
||||
const cliResults = defaultOptions;
|
||||
|
||||
const program = new Command().name(CREATE_TRIGGER);
|
||||
|
||||
program
|
||||
.description("A CLI for creating Trigger.dev projects")
|
||||
.argument(
|
||||
"[template-name]",
|
||||
"The name of the template to use, e.g. basic-starter",
|
||||
"blank-starter"
|
||||
)
|
||||
.option(
|
||||
"-p, --projectName <project-name>",
|
||||
"The name of the project, as well as the name of the directory to create. Can be a path to a directory, e.g. ~/projects/my-project",
|
||||
false
|
||||
)
|
||||
.option(
|
||||
"-k, --apiKey <api-key>",
|
||||
"The development API key to use for the project. Visit https://app.trigger.dev to get yours",
|
||||
false
|
||||
)
|
||||
.option(
|
||||
"--noGit",
|
||||
"Explicitly tell the CLI to not initialize a new git repo in the project",
|
||||
false
|
||||
)
|
||||
.option(
|
||||
"--noInstall",
|
||||
"Explicitly tell the CLI to not run the package manager's install command",
|
||||
false
|
||||
)
|
||||
.option(
|
||||
"--noTelemetry",
|
||||
"Explicitly tell the CLI to not send usage data to Trigger.dev",
|
||||
false
|
||||
)
|
||||
.version(getVersion(), "-v, --version", "Display the version number")
|
||||
.addHelpText(
|
||||
"afterAll",
|
||||
`\n The create-trigger CLI was inspired by ${chalk
|
||||
.hex("#E8DCFF")
|
||||
.bold("create-t3-stack")} \n`
|
||||
)
|
||||
.parse(process.argv);
|
||||
|
||||
const templateName = program.args[0];
|
||||
|
||||
if (templateName) {
|
||||
cliResults.templateName = templateName;
|
||||
}
|
||||
|
||||
cliResults.flags = program.opts();
|
||||
|
||||
try {
|
||||
if (
|
||||
process.env.SHELL?.toLowerCase().includes("git") &&
|
||||
process.env.SHELL?.includes("bash")
|
||||
) {
|
||||
logger.warn(` WARNING: It looks like you are using Git Bash which is non-interactive. Please run create-t3-app with another
|
||||
terminal such as Windows Terminal or PowerShell if you want to use the interactive CLI.`);
|
||||
|
||||
const error = new Error("Non-interactive environment");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(error as any).isTTYError = true;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!templateName) {
|
||||
cliResults.templateName = await promptTemplateName(
|
||||
cliResults.templateName
|
||||
);
|
||||
}
|
||||
|
||||
if (!cliResults.flags.projectName) {
|
||||
cliResults.flags.projectName = await promptProjectName();
|
||||
}
|
||||
|
||||
if (!cliResults.flags.apiKey) {
|
||||
cliResults.flags.apiKey = await promptApiKey();
|
||||
}
|
||||
|
||||
if (!cliResults.flags.noGit) {
|
||||
cliResults.flags.noGit = !(await promptGit());
|
||||
}
|
||||
|
||||
if (!cliResults.flags.noInstall) {
|
||||
cliResults.flags.noInstall = !(await promptInstall());
|
||||
}
|
||||
} catch (err) {
|
||||
// If the user is not calling create-trigger from an interactive terminal, inquirer will throw an error with isTTYError = true
|
||||
// If this happens, we catch the error, tell the user what has happened, and then continue to run the program with a default trigger project
|
||||
// Otherwise we have to do some fancy namespace extension logic on the Error type which feels overkill for one line
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (err instanceof Error && (err as any).isTTYError) {
|
||||
logger.warn(
|
||||
`${CREATE_TRIGGER} needs an interactive terminal to provide options`
|
||||
);
|
||||
|
||||
const { shouldContinue } = await inquirer.prompt<{
|
||||
shouldContinue: boolean;
|
||||
}>({
|
||||
name: "shouldContinue",
|
||||
type: "confirm",
|
||||
message: `Continue creating a trigger.dev project?`,
|
||||
default: true,
|
||||
});
|
||||
|
||||
if (!shouldContinue) {
|
||||
logger.info("Exiting...");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Bootstrapping the default Trigger.dev template in ./${cliResults.templateName}`
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return cliResults;
|
||||
};
|
||||
|
||||
const promptTemplateName = async (
|
||||
defaultTemplateName: string
|
||||
): Promise<string> => {
|
||||
const templates = await getTemplates();
|
||||
|
||||
if (templates.length === 0) {
|
||||
return defaultTemplateName;
|
||||
}
|
||||
|
||||
const defaultTemplate = templates.find(
|
||||
(template) => template.slug === defaultTemplateName
|
||||
);
|
||||
|
||||
const templateChoicesWithoutDefault = templates
|
||||
.filter((template) => template.slug !== defaultTemplateName)
|
||||
.map((template) => ({
|
||||
name: `${template.shortTitle} - ${template.description} [${terminalLink(
|
||||
"View more",
|
||||
template.repositoryUrl
|
||||
)}]`,
|
||||
value: template.slug,
|
||||
}));
|
||||
|
||||
const separator = new inquirer.Separator();
|
||||
|
||||
const choices = defaultTemplate
|
||||
? [
|
||||
{
|
||||
name: `${defaultTemplate.shortTitle} - ${
|
||||
defaultTemplate.description
|
||||
} [${terminalLink("View more", defaultTemplate.repositoryUrl)}]`,
|
||||
value: defaultTemplate.slug,
|
||||
},
|
||||
separator,
|
||||
...templateChoicesWithoutDefault,
|
||||
]
|
||||
: templateChoicesWithoutDefault;
|
||||
|
||||
const { templateName } = await inquirer.prompt<{ templateName: string }>({
|
||||
name: "templateName",
|
||||
type: "list",
|
||||
message: "What template would you like to use?",
|
||||
choices,
|
||||
default: defaultTemplateName,
|
||||
});
|
||||
|
||||
logger.success(`Great! We're using the ${templateName} template`);
|
||||
|
||||
return templateName;
|
||||
};
|
||||
|
||||
const promptProjectName = async (): Promise<string> => {
|
||||
const { projectName } = await inquirer.prompt<{ projectName: string }>({
|
||||
name: "projectName",
|
||||
type: "input",
|
||||
message: "What would you like to name your project?",
|
||||
default: DEFAULT_PROJECT_NAME,
|
||||
});
|
||||
|
||||
logger.success(`Great! We're creating your project at ${projectName}`);
|
||||
|
||||
return projectName;
|
||||
};
|
||||
|
||||
const promptApiKey = async (): Promise<string | undefined> => {
|
||||
// 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 | undefined }>({
|
||||
type: "input",
|
||||
name: "apiKey",
|
||||
message: "Enter your development API key (optional)",
|
||||
default: undefined,
|
||||
validate: (input) => {
|
||||
// Make sure they enter something like trigger_development_******** or trigger_dev_********
|
||||
if (input) {
|
||||
if (
|
||||
!input.startsWith("trigger_development_") ||
|
||||
!input.startsWith("trigger_dev_")
|
||||
) {
|
||||
return "Please enter a valid development API key or leave blank to skip";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
if (apiKey) {
|
||||
logger.success(
|
||||
`Fantastic! We'll save the API key (${obfuscateApiKey(
|
||||
apiKey
|
||||
)}) in the .env file.`
|
||||
);
|
||||
}
|
||||
|
||||
return apiKey;
|
||||
};
|
||||
|
||||
const promptGit = async (): Promise<boolean> => {
|
||||
const { git } = await inquirer.prompt<{ git: boolean }>({
|
||||
name: "git",
|
||||
type: "confirm",
|
||||
message: "Initialize a new git repository?",
|
||||
default: true,
|
||||
});
|
||||
|
||||
if (git) {
|
||||
logger.success("Nice one! Initializing repository!");
|
||||
} else {
|
||||
logger.info("Sounds good! You can come back and run git init later.");
|
||||
}
|
||||
|
||||
return git;
|
||||
};
|
||||
|
||||
const promptInstall = async (): Promise<boolean> => {
|
||||
const pkgManager = getUserPkgManager();
|
||||
|
||||
const { install } = await inquirer.prompt<{ install: boolean }>({
|
||||
name: "install",
|
||||
type: "confirm",
|
||||
message:
|
||||
`Would you like us to run '${pkgManager}` +
|
||||
(pkgManager === "yarn" ? `'?` : ` install'?`),
|
||||
default: true,
|
||||
});
|
||||
|
||||
if (install) {
|
||||
logger.success("Alright. We'll install the dependencies for you!");
|
||||
} else {
|
||||
if (pkgManager === "yarn") {
|
||||
logger.info(
|
||||
`No worries. You can run '${pkgManager}' later to install the dependencies.`
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`No worries. You can run '${pkgManager} install' later to install the dependencies.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return install;
|
||||
};
|
||||
|
||||
export const obfuscateApiKey = (apiKey: string) => {
|
||||
const [prefix, slug, secretPart] = apiKey.split("_") as [
|
||||
string,
|
||||
string,
|
||||
string
|
||||
];
|
||||
return `${prefix}_${slug}_${"*".repeat(secretPart.length)}`;
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { runCli } from "./cli/index.js";
|
||||
import { createProject } from "./utils/createProject.js";
|
||||
import { logger } from "./utils/logger.js";
|
||||
import { renderTitle } from "./utils/renderTitle.js";
|
||||
import { createTemplateRef } from "./utils/templateRef.js";
|
||||
import { installDependencies } from "./utils/installDependencies.js";
|
||||
import { initializeGit } from "./utils/git.js";
|
||||
import { parseNameAndPath } from "./utils/parseNameAndPath.js";
|
||||
import { logNextSteps } from "./utils/logNextSteps.js";
|
||||
import { createDotEnvFile } from "./utils/createDotEnvFile.js";
|
||||
import { sendTelemetry } from "./utils/triggerApi.js";
|
||||
import { createTelemetryEvent } from "./utils/createTelemetryEvent.js";
|
||||
|
||||
const main = async () => {
|
||||
renderTitle();
|
||||
|
||||
const cli = await runCli();
|
||||
|
||||
const repositoryRef = createTemplateRef(cli.templateName);
|
||||
|
||||
const [scopedProjectName, projectDir] = parseNameAndPath(
|
||||
cli.flags.projectName
|
||||
);
|
||||
|
||||
const projectPath = await createProject(
|
||||
repositoryRef,
|
||||
projectDir,
|
||||
scopedProjectName ?? cli.templateName
|
||||
);
|
||||
|
||||
if (!projectPath) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!cli.flags.noInstall) {
|
||||
await installDependencies(projectPath);
|
||||
}
|
||||
|
||||
if (!cli.flags.noGit) {
|
||||
await initializeGit(projectPath);
|
||||
}
|
||||
|
||||
await createDotEnvFile(projectPath, cli.flags.apiKey);
|
||||
|
||||
await logNextSteps({
|
||||
projectName: projectDir,
|
||||
noInstall: cli.flags.noInstall,
|
||||
apiKey: cli.flags.apiKey,
|
||||
});
|
||||
|
||||
if (!cli.flags.noTelemetry) {
|
||||
await sendTelemetry(createTelemetryEvent(cli), cli.flags.apiKey);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
main().catch((err) => {
|
||||
logger.error("Aborting installation...");
|
||||
if (err instanceof Error) {
|
||||
logger.error(err);
|
||||
} else {
|
||||
logger.error(
|
||||
"An unknown error has occurred. Please open an issue on github with the below:"
|
||||
);
|
||||
console.log(err);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
import { DEFAULT_APP_NAME, TRIGGER_BASE_URL } from "../consts.js";
|
||||
import { getUserPkgManager } from "./getUserPkgManager.js";
|
||||
import { logger } from "./logger.js";
|
||||
import { whoami } from "./triggerApi.js";
|
||||
|
||||
// This logs the next steps that the user should take in order to advance the project
|
||||
export async function logNextSteps({
|
||||
projectName = DEFAULT_APP_NAME,
|
||||
noInstall,
|
||||
apiKey,
|
||||
}: {
|
||||
projectName: string;
|
||||
noInstall: boolean;
|
||||
apiKey?: string;
|
||||
}) {
|
||||
const pkgManager = getUserPkgManager();
|
||||
|
||||
logger.info("Next steps:");
|
||||
projectName !== "." && logger.info(` cd ${projectName}`);
|
||||
if (noInstall) {
|
||||
// To reflect yarn's default behavior of installing packages when no additional args provided
|
||||
if (pkgManager === "yarn") {
|
||||
logger.info(` ${pkgManager}`);
|
||||
} else {
|
||||
logger.info(` ${pkgManager} install`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
logger.info(
|
||||
` visit ${TRIGGER_BASE_URL} to get your development API key and update your .env file`
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(` ${pkgManager === "npm" ? "npm run" : pkgManager} dev`);
|
||||
|
||||
if (apiKey) {
|
||||
const org = await whoami(apiKey);
|
||||
|
||||
if (org) {
|
||||
logger.info(
|
||||
` visit ${TRIGGER_BASE_URL}/orgs/${org.organizationSlug} to see your triggers`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import pathModule from "path";
|
||||
|
||||
/**
|
||||
* Parses the projectName and its path from the user input.
|
||||
*
|
||||
* Returns a tuple of of `[projectName, path]`, where `projectName` is the name put in the "package.json"
|
||||
* file and `path` is the path to the directory where the project will be created.
|
||||
*
|
||||
* If `projectName` is ".", the name of the directory will be used instead. Handles the case where the
|
||||
* input includes a scoped package name in which case that is being parsed as the name, but not
|
||||
* included as the path.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* - dir/@mono/app => ["@mono/app", "dir/app"]
|
||||
* - dir/app => ["app", "dir/app"]
|
||||
*/
|
||||
export const parseNameAndPath = (input: string) => {
|
||||
const paths = input.split("/");
|
||||
|
||||
let projectName = paths[paths.length - 1];
|
||||
|
||||
// If the user ran `npx create-t3-app .` or similar, the projectName should be the current directory
|
||||
if (projectName === ".") {
|
||||
const parsedCwd = pathModule.resolve(process.cwd());
|
||||
projectName = pathModule.basename(parsedCwd);
|
||||
}
|
||||
|
||||
// If the first part is a @, it's a scoped package
|
||||
const indexOfDelimiter = paths.findIndex((p) => p.startsWith("@"));
|
||||
if (paths.findIndex((p) => p.startsWith("@")) !== -1) {
|
||||
projectName = paths.slice(indexOfDelimiter).join("/");
|
||||
}
|
||||
|
||||
const path = paths.filter((p) => !p.startsWith("@")).join("/");
|
||||
|
||||
return [projectName, path] as const;
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
import fetch from "node-fetch";
|
||||
import { TRIGGER_BASE_URL } from "../consts.js";
|
||||
|
||||
export type WhoamiResponse = {
|
||||
organizationId: number;
|
||||
env: string;
|
||||
organizationSlug: string;
|
||||
};
|
||||
|
||||
export async function whoami(
|
||||
apiKey: string
|
||||
): Promise<WhoamiResponse | undefined> {
|
||||
const response = await fetch(`${TRIGGER_BASE_URL}/api/v1/internal/whoami`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
return response.json() as Promise<WhoamiResponse>;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export type TriggerTemplate = {
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
shortTitle: string;
|
||||
description: string;
|
||||
imageUrl: string;
|
||||
repositoryUrl: string;
|
||||
markdownDocs: string;
|
||||
runLocalDocs: string;
|
||||
priority: number;
|
||||
services: string[];
|
||||
workflowIds: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export async function getTemplates(): Promise<Array<TriggerTemplate>> {
|
||||
const response = await fetch(`${TRIGGER_BASE_URL}/api/v1/templates`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
return response.json() as Promise<Array<TriggerTemplate>>;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export type TelemetryEvent = {
|
||||
id: string;
|
||||
event: string;
|
||||
properties: Record<string | number, any>;
|
||||
};
|
||||
|
||||
export async function sendTelemetry(event: TelemetryEvent, apiKey?: string) {
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/json",
|
||||
};
|
||||
|
||||
if (apiKey) {
|
||||
headers.Authorization = `Bearer ${apiKey}`;
|
||||
}
|
||||
|
||||
await fetch(`${TRIGGER_BASE_URL}/api/v1/internal/telemetry`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(event),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
## ✨ @trigger.dev/init - Initialize your Next.js 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.
|
||||
|
||||
## 💻 Usage
|
||||
|
||||
To initialize your Next.js project using `@trigger.dev/init`, run any of the following three commands and answer the prompts:
|
||||
|
||||
### npm
|
||||
|
||||
```sh
|
||||
npx @trigger.dev/init@latest
|
||||
```
|
||||
|
||||
### yarn
|
||||
|
||||
```sh
|
||||
yarn @trigger.dev/init@latest
|
||||
```
|
||||
|
||||
### pnpm
|
||||
|
||||
```sh
|
||||
pnpm dlx @trigger.dev/init@latest
|
||||
```
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "create-trigger",
|
||||
"name": "@trigger.dev/init",
|
||||
"version": "0.2.0",
|
||||
"description": "The Trigger.dev CLI to easily create and manage a Trigger.dev project",
|
||||
"description": "The CLI to easily initialize Trigger.dev in your Next.js project",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/triggerdotdev/trigger.dev.git",
|
||||
"directory": "packages/create-trigger"
|
||||
"directory": "packages/init-trigger"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -21,7 +21,10 @@
|
||||
"events",
|
||||
"webhooks",
|
||||
"integrations",
|
||||
"apis"
|
||||
"apis",
|
||||
"jobs",
|
||||
"background-jobs",
|
||||
"nextjs"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
@@ -29,7 +32,7 @@
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
"bin": {
|
||||
"create-trigger": "./dist/index.js"
|
||||
"init": "./dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
@@ -60,9 +63,10 @@
|
||||
"inquirer": "^9.1.4",
|
||||
"node-fetch": "^3.3.0",
|
||||
"ora": "^6.1.2",
|
||||
"simple-git": "^3.19.0",
|
||||
"terminal-link": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import { Command } from "commander";
|
||||
import inquirer from "inquirer";
|
||||
import { COMMAND_NAME } from "../consts.js";
|
||||
import { getVersion } from "../utils/getVersion.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
|
||||
export interface CliFlags {
|
||||
projectPath: string;
|
||||
triggerUrl: string;
|
||||
endpointUrl: string;
|
||||
endpointSlug: string;
|
||||
apiKey?: string;
|
||||
}
|
||||
|
||||
export interface CliResults {
|
||||
flags: CliFlags;
|
||||
}
|
||||
|
||||
const defaultOptions: CliResults = {
|
||||
flags: {
|
||||
projectPath: ".",
|
||||
triggerUrl: "https://cloud.trigger.dev",
|
||||
endpointUrl: "http://localhost:3000",
|
||||
endpointSlug: "my-nextjs-project",
|
||||
},
|
||||
};
|
||||
|
||||
export const parseCliOptions = async () => {
|
||||
const cliResults = defaultOptions;
|
||||
|
||||
const program = new Command().name(COMMAND_NAME);
|
||||
|
||||
program
|
||||
.description("A CLI for initializing Trigger.dev in your Next.js")
|
||||
.option(
|
||||
"-p, --project-path <project-path>",
|
||||
"The path to the Next.js project",
|
||||
"."
|
||||
)
|
||||
.option(
|
||||
"-k, --api-key <api-key>",
|
||||
"The development API key to use for the project.",
|
||||
false
|
||||
)
|
||||
.option(
|
||||
"-e, --endpoint-slug <endpoint-slug>",
|
||||
"The unique slug for the endpoint to use for this project. (e.g. my-nextjs-project)",
|
||||
false
|
||||
)
|
||||
.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",
|
||||
false
|
||||
)
|
||||
.option(
|
||||
"-t, --trigger-url <trigger-url>",
|
||||
"The URL of the Trigger.dev instance to use. (e.g. https://cloud.trigger.dev)",
|
||||
false
|
||||
)
|
||||
.version(getVersion(), "-v, --version", "Display the version number")
|
||||
.parse(process.argv);
|
||||
|
||||
cliResults.flags = program.opts();
|
||||
|
||||
return cliResults;
|
||||
};
|
||||
|
||||
export const runCliPrompts = async (cliResults: CliResults) => {
|
||||
try {
|
||||
if (!cliResults.flags.apiKey) {
|
||||
cliResults.flags.apiKey = await promptApiKey();
|
||||
}
|
||||
|
||||
if (!cliResults.flags.endpointSlug) {
|
||||
cliResults.flags.endpointSlug = await promptEndpointSlug();
|
||||
}
|
||||
} catch (err) {
|
||||
// If the user is not calling the command from an interactive terminal, inquirer will throw an error with isTTYError = true
|
||||
// If this happens, we catch the error, tell the user what has happened, and then continue to run the program with a default trigger project
|
||||
// Otherwise we have to do some fancy namespace extension logic on the Error type which feels overkill for one line
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (err instanceof Error && (err as any).isTTYError) {
|
||||
logger.warn(
|
||||
`${COMMAND_NAME} needs an interactive terminal to provide options`
|
||||
);
|
||||
|
||||
const { shouldContinue } = await inquirer.prompt<{
|
||||
shouldContinue: boolean;
|
||||
}>({
|
||||
name: "shouldContinue",
|
||||
type: "confirm",
|
||||
message: `Continue initializing your trigger.dev project?`,
|
||||
default: true,
|
||||
});
|
||||
|
||||
if (!shouldContinue) {
|
||||
logger.info("Exiting...");
|
||||
process.exit(0);
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return cliResults;
|
||||
};
|
||||
|
||||
const promptApiKey = async (): 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)",
|
||||
validate: (input) => {
|
||||
// Make sure they enter something like tr_dev_********
|
||||
if (!input) {
|
||||
return "Please enter your development API key";
|
||||
}
|
||||
|
||||
if (!input.startsWith("tr_dev_")) {
|
||||
return "Please enter a valid development API key or leave blank to skip (should start with tr_dev_)";
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
return apiKey;
|
||||
};
|
||||
|
||||
const promptEndpointSlug = async (): Promise<string> => {
|
||||
const { endpointSlug } = await inquirer.prompt<{
|
||||
endpointSlug: string;
|
||||
}>({
|
||||
type: "input",
|
||||
name: "endpointSlug",
|
||||
message: "Enter a unique slug for your endpoint (required)",
|
||||
validate: (input) => {
|
||||
if (!input) {
|
||||
return "Please enter a unique slug for your endpoint";
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
return endpointSlug;
|
||||
};
|
||||
|
||||
export const obfuscateApiKey = (apiKey: string) => {
|
||||
const [prefix, slug, secretPart] = apiKey.split("_") as [
|
||||
string,
|
||||
string,
|
||||
string
|
||||
];
|
||||
return `${prefix}_${slug}_${"*".repeat(secretPart.length)}`;
|
||||
};
|
||||
@@ -16,7 +16,7 @@ export const TITLE_TEXT = `
|
||||
`;
|
||||
|
||||
export const DEFAULT_APP_NAME = "my-triggers";
|
||||
export const CREATE_TRIGGER = "create-trigger";
|
||||
export const COMMAND_NAME = "@trigger.dev/init";
|
||||
export const TEMPLATE_ORGANIZATION = "triggerdotdev";
|
||||
export const TRIGGER_BASE_URL =
|
||||
process.env.TRIGGER_BASE_URL ?? "https://app.trigger.dev";
|
||||
process.env.TRIGGER_BASE_URL ?? "https://cloud.trigger.dev";
|
||||
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { CliResults, parseCliOptions, runCliPrompts } from "./cli/index.js";
|
||||
import { addDependencies } from "./utils/addDependencies.js";
|
||||
import { logger } from "./utils/logger.js";
|
||||
import { resolvePath } from "./utils/parseNameAndPath.js";
|
||||
import { renderTitle } from "./utils/renderTitle.js";
|
||||
import fs from "fs/promises";
|
||||
import pathModule from "path";
|
||||
import { simpleGit } from "simple-git";
|
||||
import { TriggerApi } from "./utils/triggerApi.js";
|
||||
|
||||
const main = async () => {
|
||||
renderTitle();
|
||||
|
||||
const cliOptions = await parseCliOptions();
|
||||
|
||||
const resolvedPath = resolvePath(cliOptions.flags.projectPath);
|
||||
// Detect if are are in a Next.js project
|
||||
const isNextJsProject = await detectNextJsProject(resolvedPath);
|
||||
|
||||
if (!isNextJsProject) {
|
||||
logger.error("You must run this command in a Next.js project.");
|
||||
process.exit(1);
|
||||
} else {
|
||||
logger.success("✅ Detected Next.js project");
|
||||
}
|
||||
|
||||
const hasGitChanges = await detectGitChanges(resolvedPath);
|
||||
|
||||
if (hasGitChanges) {
|
||||
// Warn the user that they have git changes
|
||||
logger.warn(
|
||||
"⚠️ You have uncommitted git changes, you may want to commit them before continuing."
|
||||
);
|
||||
}
|
||||
|
||||
const isTypescriptProject = await detectTypescriptProject(resolvedPath);
|
||||
|
||||
if (!isTypescriptProject) {
|
||||
// Exit with an error message
|
||||
logger.error(
|
||||
"You must be using TypeScript in your Next.js project to use Trigger.dev."
|
||||
);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cliResults = await runCliPrompts(cliOptions);
|
||||
|
||||
const apiKey = cliResults.flags.apiKey;
|
||||
|
||||
if (!apiKey) {
|
||||
logger.error("You must provide an API key to continue.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await addDependencies(resolvedPath, [
|
||||
{ name: "@trigger.dev/sdk", version: "next" },
|
||||
{ name: "@trigger.dev/nextjs", version: "next" },
|
||||
]);
|
||||
|
||||
// Setup environment variables
|
||||
const addedEnvVars = await setupEnvironmentVariables(
|
||||
resolvedPath,
|
||||
cliResults
|
||||
);
|
||||
logger.success(`✅ Setup environment variables ${addedEnvVars.join(", ")}`);
|
||||
|
||||
const nextJsDir = await detectPagesOrAppDir(resolvedPath);
|
||||
|
||||
if (nextJsDir === "pages") {
|
||||
await createTriggerPageRoute(resolvedPath, cliResults);
|
||||
} else {
|
||||
await createTriggerAppRoute(resolvedPath, cliResults);
|
||||
}
|
||||
|
||||
const api = new TriggerApi(apiKey, cliResults.flags.triggerUrl);
|
||||
|
||||
const endpoint = await api.createEndpoint({
|
||||
id: cliResults.flags.endpointSlug,
|
||||
url: `${cliResults.flags.endpointUrl}${
|
||||
cliResults.flags.endpointUrl.endsWith("/") ? "" : "/"
|
||||
}api/trigger`,
|
||||
});
|
||||
|
||||
if (!endpoint) {
|
||||
logger.error(
|
||||
"Unable to create endpoint, please contact eric@trigger.dev for assistance."
|
||||
);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
logger.success(`✅ Successfully initialized Trigger.dev!`);
|
||||
logger.info(
|
||||
`🔗 Visit your Trigger.dev dashboard at ${cliResults.flags.triggerUrl}`
|
||||
);
|
||||
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
main().catch((err) => {
|
||||
logger.error("Aborting installation...");
|
||||
if (err instanceof Error) {
|
||||
logger.error(err);
|
||||
} else {
|
||||
logger.error(
|
||||
"An unknown error has occurred. Please open an issue on github with the below:"
|
||||
);
|
||||
console.log(err);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Detects if the project is a Next.js project at path
|
||||
async function detectNextJsProject(path: string): Promise<boolean> {
|
||||
// Checks for the presence of a next.config.js file
|
||||
try {
|
||||
// Check if next.config.js file exists in the given path
|
||||
await fs.access(pathModule.join(path, "next.config.js"));
|
||||
return true;
|
||||
} catch (error) {
|
||||
// If next.config.js file doesn't exist, it's not a Next.js project
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Detects if there are any uncommitted git changes at path
|
||||
async function detectGitChanges(path: string): Promise<boolean> {
|
||||
const git = simpleGit(path);
|
||||
const status = await git.status();
|
||||
|
||||
return status.files.length > 0;
|
||||
}
|
||||
|
||||
async function detectTypescriptProject(path: string): Promise<boolean> {
|
||||
// Checks for the presence of a tsconfig.json file
|
||||
try {
|
||||
await fs.access(pathModule.join(path, "tsconfig.json"));
|
||||
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): Promise<"pages" | "app"> {
|
||||
const nextConfigPath = pathModule.join(path, "next.config.js");
|
||||
const importedConfig = await import(nextConfigPath);
|
||||
|
||||
if (importedConfig?.default?.experimental?.appDir) {
|
||||
return "app";
|
||||
} else {
|
||||
return "pages";
|
||||
}
|
||||
}
|
||||
|
||||
async function createTriggerPageRoute(path: string, cliResults: CliResults) {
|
||||
const routeContent = `
|
||||
import { Job, TriggerClient, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { createPagesRoute } from "@trigger.dev/nextjs";
|
||||
|
||||
const { handler, config } = createPagesRoute(client, { path: "/api/trigger" });
|
||||
export { config };
|
||||
|
||||
const client = new TriggerClient({
|
||||
id: "${cliResults.flags.endpointSlug}",
|
||||
url: process.env.VERCEL_URL,
|
||||
apiKey: process.env.TRIGGER_API_KEY,
|
||||
apiUrl: process.env.TRIGGER_API_URL,
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "example-job",
|
||||
name: "Example Job",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "example.event",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("Hello world!", { payload });
|
||||
|
||||
return {
|
||||
message: "Hello world!",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default handler;
|
||||
`;
|
||||
|
||||
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, "trigger.ts"));
|
||||
|
||||
if (exists) {
|
||||
logger.info("Skipping creation of pages route because it already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.writeFile(pathModule.join(directories, "trigger.ts"), routeContent);
|
||||
logger.success("✅ Create pages route at /pages/api/trigger.ts");
|
||||
}
|
||||
|
||||
async function createTriggerAppRoute(path: string, cliResults: CliResults) {
|
||||
const routeContent = `
|
||||
import { Job, TriggerClient, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { createAppRoute } from "@trigger.dev/nextjs";
|
||||
|
||||
const client = new TriggerClient({
|
||||
id: "${cliResults.flags.endpointSlug}",
|
||||
url: process.env.VERCEL_URL,
|
||||
apiKey: process.env.TRIGGER_API_KEY,
|
||||
apiUrl: process.env.TRIGGER_API_URL,
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "example-job",
|
||||
name: "Example Job",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "example.event",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("Hello world!", { payload });
|
||||
|
||||
return {
|
||||
message: "Hello world!",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const { POST, dynamic } = createAppRoute(client, {
|
||||
path: "/api/trigger",
|
||||
});
|
||||
`;
|
||||
|
||||
const directories = pathModule.join(path, "app", "api", "trigger");
|
||||
await fs.mkdir(directories, { recursive: true });
|
||||
|
||||
const fileExists = await pathExists(pathModule.join(directories, "route.ts"));
|
||||
|
||||
if (fileExists) {
|
||||
logger.info("Skipping creation of app route because it already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.writeFile(pathModule.join(directories, "route.ts"), routeContent);
|
||||
logger.success("✅ Create app route at /app/api/trigger/route.ts");
|
||||
}
|
||||
|
||||
type EnvironmentVariable = "TRIGGER_API_KEY" | "TRIGGER_API_URL" | "VERCEL_URL";
|
||||
|
||||
async function setupEnvironmentVariables(
|
||||
path: string,
|
||||
cliResults: CliResults
|
||||
): Promise<Array<EnvironmentVariable>> {
|
||||
let results: Array<EnvironmentVariable> = [];
|
||||
|
||||
const envFilePath = pathModule.join(path, ".env.local");
|
||||
const envFileExists = await pathExists(envFilePath);
|
||||
|
||||
if (envFileExists) {
|
||||
const envFileContent = await fs.readFile(envFilePath, "utf-8");
|
||||
|
||||
if (envFileContent.includes("TRIGGER_API_KEY")) {
|
||||
// Update the existing value
|
||||
const updatedEnvFileContent = envFileContent.replace(
|
||||
/TRIGGER_API_KEY=.*/g,
|
||||
`TRIGGER_API_KEY=${cliResults.flags.apiKey}`
|
||||
);
|
||||
|
||||
await fs.writeFile(envFilePath, updatedEnvFileContent);
|
||||
results.push("TRIGGER_API_KEY");
|
||||
} else {
|
||||
await fs.appendFile(
|
||||
envFilePath,
|
||||
`TRIGGER_API_KEY=${cliResults.flags.apiKey}\n`
|
||||
);
|
||||
results.push("TRIGGER_API_KEY");
|
||||
}
|
||||
|
||||
if (envFileContent.includes("TRIGGER_API_URL")) {
|
||||
// Update existing value
|
||||
const updatedEnvFileContent = envFileContent.replace(
|
||||
/TRIGGER_API_URL=.*/g,
|
||||
`TRIGGER_API_URL=${cliResults.flags.triggerUrl}`
|
||||
);
|
||||
|
||||
await fs.writeFile(envFilePath, updatedEnvFileContent);
|
||||
results.push("TRIGGER_API_URL");
|
||||
} else {
|
||||
await fs.appendFile(
|
||||
envFilePath,
|
||||
`TRIGGER_API_URL=${cliResults.flags.triggerUrl}\n`
|
||||
);
|
||||
results.push("TRIGGER_API_URL");
|
||||
}
|
||||
|
||||
if (!envFileContent.includes("VERCEL_URL")) {
|
||||
await fs.appendFile(
|
||||
envFilePath,
|
||||
`VERCEL_URL=${cliResults.flags.endpointUrl}\n`
|
||||
);
|
||||
results.push("VERCEL_URL");
|
||||
}
|
||||
} else {
|
||||
const envFileContent = `
|
||||
TRIGGER_API_KEY=${cliResults.flags.apiKey}
|
||||
TRIGGER_API_URL=${cliResults.flags.triggerUrl}
|
||||
VERCEL_URL=${cliResults.flags.endpointUrl}
|
||||
`;
|
||||
|
||||
await fs.writeFile(envFilePath, envFileContent);
|
||||
results = ["TRIGGER_API_KEY", "TRIGGER_API_URL", "VERCEL_URL"];
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async function pathExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+37
-6
@@ -1,23 +1,54 @@
|
||||
import { getUserPkgManager, type PackageManager } from "./getUserPkgManager.js";
|
||||
import { logger } from "./logger.js";
|
||||
import ora, { type Ora } from "ora";
|
||||
import chalk from "chalk";
|
||||
import { execa } from "execa";
|
||||
import ora, { type Ora } from "ora";
|
||||
import pathModule from "path";
|
||||
import { getUserPkgManager, type PackageManager } from "./getUserPkgManager.js";
|
||||
import fs from "fs/promises";
|
||||
|
||||
export async function installDependencies(projectDir: string) {
|
||||
logger.info("Installing dependencies...");
|
||||
export type InstallPackage = {
|
||||
name: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export async function addDependencies(
|
||||
projectDir: string,
|
||||
packages: Array<InstallPackage>
|
||||
) {
|
||||
const pkgManager = getUserPkgManager();
|
||||
|
||||
await addDependenciesToPackageJson(projectDir, packages);
|
||||
|
||||
const installSpinner = await runInstallCommand(pkgManager, projectDir);
|
||||
|
||||
// If the spinner was used to show the progress, use succeed method on it
|
||||
// If not, use the succeed on a new spinner
|
||||
(installSpinner || ora()).succeed(
|
||||
chalk.green("Successfully installed dependencies!\n")
|
||||
chalk.green(
|
||||
`Successfully installed ${packages
|
||||
.map((pkg) => `${pkg.name}@${pkg.version}`)
|
||||
.join(", ")}}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function addDependenciesToPackageJson(
|
||||
projectDir: string,
|
||||
packages: Array<InstallPackage>
|
||||
) {
|
||||
const pkgJsonPath = pathModule.join(projectDir, "package.json");
|
||||
const pkgBuffer = await fs.readFile(pkgJsonPath);
|
||||
const pkgJson = JSON.parse(pkgBuffer.toString());
|
||||
|
||||
// Add the dependencies to the package.json file
|
||||
pkgJson.dependencies = {
|
||||
...pkgJson.dependencies,
|
||||
...Object.fromEntries(packages.map((pkg) => [pkg.name, pkg.version])),
|
||||
};
|
||||
|
||||
// Write the updated package.json file
|
||||
await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
||||
}
|
||||
|
||||
async function runInstallCommand(
|
||||
pkgManager: PackageManager,
|
||||
projectDir: string
|
||||
@@ -0,0 +1,6 @@
|
||||
import pathModule from "path";
|
||||
|
||||
// Takes a relative path (like .) and resolves it to a full path (like /Users/username/Projects/my-triggers)
|
||||
export const resolvePath = (input: string) => {
|
||||
return pathModule.resolve(process.cwd(), input);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import fetch from "node-fetch";
|
||||
|
||||
export type CreateEndpointOptions = {
|
||||
id: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type EndpointResponse = {
|
||||
id: string;
|
||||
slug: string;
|
||||
url: string;
|
||||
environmentId: string;
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
indexingHookIdentifier: string;
|
||||
};
|
||||
|
||||
export class TriggerApi {
|
||||
constructor(private apiKey: string, private baseUrl: string) {}
|
||||
|
||||
async createEndpoint(
|
||||
options: CreateEndpointOptions
|
||||
): Promise<EndpointResponse | undefined> {
|
||||
const response = await fetch(`${this.baseUrl}/api/v1/endpoints`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify(options),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
return response.json() as Promise<EndpointResponse>;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
Generated
+125
-46
@@ -533,6 +533,9 @@ importers:
|
||||
'@radix-ui/react-popover': ^1.0.5
|
||||
'@radix-ui/react-slot': ^1.0.2
|
||||
'@trigger.dev/companyicons': ^1.5.9
|
||||
'@trigger.dev/init': workspace:*
|
||||
'@trigger.dev/nextjs': next
|
||||
'@trigger.dev/sdk': next
|
||||
'@types/node': 20.3.1
|
||||
'@types/react': ^18.0.21
|
||||
'@types/react-dom': ^18.0.6
|
||||
@@ -557,6 +560,8 @@ importers:
|
||||
'@radix-ui/react-popover': 1.0.5_ib3m5ricvtkl2cll7qpr2f6lvq
|
||||
'@radix-ui/react-slot': 1.0.2_kzbn2opkn2327fwg5yzwzya5o4
|
||||
'@trigger.dev/companyicons': 1.5.9_biqbaboplfbrettd7655fr4n2y
|
||||
'@trigger.dev/nextjs': 1.0.0-next.0_ro477i2ikctu7zbnxdidq5gjqe
|
||||
'@trigger.dev/sdk': 2.0.0-next.1
|
||||
'@types/node': 20.3.1
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
@@ -575,6 +580,8 @@ importers:
|
||||
tailwindcss: 3.3.2
|
||||
tailwindcss-animate: 1.0.5_tailwindcss@3.3.2
|
||||
typescript: 5.1.3
|
||||
devDependencies:
|
||||
'@trigger.dev/init': link:../../packages/init-trigger
|
||||
|
||||
integrations/github:
|
||||
specifiers:
|
||||
@@ -647,51 +654,6 @@ importers:
|
||||
rimraf: 3.0.2
|
||||
tsup: 6.6.3
|
||||
|
||||
packages/create-trigger:
|
||||
specifiers:
|
||||
'@types/degit': ^2.8.3
|
||||
'@types/fs-extra': ^11.0.1
|
||||
'@types/gradient-string': ^1.1.2
|
||||
'@types/inquirer': ^9.0.3
|
||||
'@types/node': '16'
|
||||
'@types/node-fetch': ^2.6.2
|
||||
chalk: ^5.2.0
|
||||
commander: ^9.4.1
|
||||
degit: ^2.8.4
|
||||
execa: ^7.0.0
|
||||
fs-extra: ^11.1.0
|
||||
gradient-string: ^2.0.2
|
||||
inquirer: ^9.1.4
|
||||
node-fetch: ^3.3.0
|
||||
ora: ^6.1.2
|
||||
rimraf: ^3.0.2
|
||||
terminal-link: ^3.0.0
|
||||
tsup: ^6.5.0
|
||||
type-fest: ^3.6.0
|
||||
typescript: ^4.9.5
|
||||
dependencies:
|
||||
'@types/degit': 2.8.3
|
||||
chalk: 5.2.0
|
||||
commander: 9.5.0
|
||||
degit: 2.8.4
|
||||
execa: 7.0.0
|
||||
fs-extra: 11.1.0
|
||||
gradient-string: 2.0.2
|
||||
inquirer: 9.1.4
|
||||
node-fetch: 3.3.0
|
||||
ora: 6.1.2
|
||||
terminal-link: 3.0.0
|
||||
devDependencies:
|
||||
'@types/fs-extra': 11.0.1
|
||||
'@types/gradient-string': 1.1.2
|
||||
'@types/inquirer': 9.0.3
|
||||
'@types/node': 16.18.11
|
||||
'@types/node-fetch': 2.6.2
|
||||
rimraf: 3.0.2
|
||||
tsup: 6.6.3_typescript@4.9.5
|
||||
type-fest: 3.6.0
|
||||
typescript: 4.9.5
|
||||
|
||||
packages/database:
|
||||
specifiers:
|
||||
'@prisma/client': ^4.16.0
|
||||
@@ -750,6 +712,53 @@ importers:
|
||||
'@types/react': 18.0.26
|
||||
typescript: 4.9.5
|
||||
|
||||
packages/init-trigger:
|
||||
specifiers:
|
||||
'@types/degit': ^2.8.3
|
||||
'@types/fs-extra': ^11.0.1
|
||||
'@types/gradient-string': ^1.1.2
|
||||
'@types/inquirer': ^9.0.3
|
||||
'@types/node': '16'
|
||||
'@types/node-fetch': ^2.6.2
|
||||
chalk: ^5.2.0
|
||||
commander: ^9.4.1
|
||||
degit: ^2.8.4
|
||||
execa: ^7.0.0
|
||||
fs-extra: ^11.1.0
|
||||
gradient-string: ^2.0.2
|
||||
inquirer: ^9.1.4
|
||||
node-fetch: ^3.3.0
|
||||
ora: ^6.1.2
|
||||
rimraf: ^3.0.2
|
||||
simple-git: ^3.19.0
|
||||
terminal-link: ^3.0.0
|
||||
tsup: ^6.5.0
|
||||
type-fest: ^3.6.0
|
||||
typescript: ^4.9.5
|
||||
dependencies:
|
||||
'@types/degit': 2.8.3
|
||||
chalk: 5.2.0
|
||||
commander: 9.5.0
|
||||
degit: 2.8.4
|
||||
execa: 7.0.0
|
||||
fs-extra: 11.1.0
|
||||
gradient-string: 2.0.2
|
||||
inquirer: 9.1.4
|
||||
node-fetch: 3.3.0
|
||||
ora: 6.1.2
|
||||
simple-git: 3.19.0
|
||||
terminal-link: 3.0.0
|
||||
devDependencies:
|
||||
'@types/fs-extra': 11.0.1
|
||||
'@types/gradient-string': 1.1.2
|
||||
'@types/inquirer': 9.0.3
|
||||
'@types/node': 16.18.11
|
||||
'@types/node-fetch': 2.6.2
|
||||
rimraf: 3.0.2
|
||||
tsup: 6.6.3_typescript@4.9.5
|
||||
type-fest: 3.6.0
|
||||
typescript: 4.9.5
|
||||
|
||||
packages/integration-kit:
|
||||
specifiers:
|
||||
'@trigger.dev/tsconfig': workspace:*
|
||||
@@ -6903,6 +6912,18 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@kwsites/file-exists/1.1.1:
|
||||
resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==}
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@kwsites/promise-deferred/1.1.1:
|
||||
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
|
||||
dev: false
|
||||
|
||||
/@lezer/common/1.0.2:
|
||||
resolution: {integrity: sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==}
|
||||
dev: false
|
||||
@@ -10558,6 +10579,46 @@ packages:
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/nextjs/1.0.0-next.0_ro477i2ikctu7zbnxdidq5gjqe:
|
||||
resolution: {integrity: sha512-mIOuGNK2os0tqAPzmixayhBDZZ15xWfr60rP2jIHs2ARIdLVARvqQS1NvTs+WhfzgevFEKDuwXliv1LVi0JDEQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@trigger.dev/sdk': ^2.0.0-next.0
|
||||
next: 13.3.1
|
||||
dependencies:
|
||||
'@trigger.dev/sdk': 2.0.0-next.1
|
||||
debug: 4.3.4
|
||||
next: 13.3.1_biqbaboplfbrettd7655fr4n2y
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/sdk/2.0.0-next.1:
|
||||
resolution: {integrity: sha512-MA7Z582x1fDaFpcinZic9Rh6V74Xv6R4yHwiH4u9VrLmsiaQfwnwK9LWXoR/ZhcwEwsCt+VG0Tce4pDmGUN8HA==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
chalk: 5.2.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.11
|
||||
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.1.0
|
||||
zod-to-json-schema: 3.20.2_zod@3.21.4
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- encoding
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@tsconfig/node10/1.0.9:
|
||||
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
||||
|
||||
@@ -10954,7 +11015,7 @@ packages:
|
||||
/@types/node-fetch/2.6.2:
|
||||
resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==}
|
||||
dependencies:
|
||||
'@types/node': 18.15.13
|
||||
'@types/node': 20.3.1
|
||||
form-data: 3.0.1
|
||||
dev: true
|
||||
|
||||
@@ -22411,6 +22472,16 @@ packages:
|
||||
/signal-exit/3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
|
||||
/simple-git/3.19.0:
|
||||
resolution: {integrity: sha512-hyH2p9Ptxjf/xPuL7HfXbpYt9gKhC1yWDh3KYIAYJJePAKV7AEjLN4xhp7lozOdNiaJ9jlVvAbBymVlcS2jRiA==}
|
||||
dependencies:
|
||||
'@kwsites/file-exists': 1.1.1
|
||||
'@kwsites/promise-deferred': 1.1.1
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/simple-oauth2/5.0.0:
|
||||
resolution: {integrity: sha512-8291lo/z5ZdpmiOFzOs1kF3cxn22bMj5FFH+DNUppLJrpoIlM1QnFiE7KpshHu3J3i21TVcx4yW+gXYjdCKDLQ==}
|
||||
dependencies:
|
||||
@@ -25280,6 +25351,14 @@ packages:
|
||||
zod: 3.20.2
|
||||
dev: false
|
||||
|
||||
/zod-to-json-schema/3.20.2_zod@3.21.4:
|
||||
resolution: {integrity: sha512-qka3UAXmVXD8z5SHkRU89UyHp8JHJW7zc8RQCpt7QloJOn6uijwkjUm8o+M/cF1IysmKc5gxac/QeDikaQMdzQ==}
|
||||
peerDependencies:
|
||||
zod: ^3.20.0
|
||||
dependencies:
|
||||
zod: 3.21.4
|
||||
dev: false
|
||||
|
||||
/zod/3.20.2:
|
||||
resolution: {integrity: sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==}
|
||||
dev: false
|
||||
|
||||
Reference in New Issue
Block a user