Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 1.4k
feat(webapp,cli,database): track real dev onboarding progress#4563
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "trigger.dev": patch | ||
| --- | ||
| The dev environment onboarding now tracks real progress. After you run `init`, the setup checklist marks your project as initialized, and it updates live as your dev server connects and your tasks register. The blank state also adds a "Copy AI agent prompt" button that copies a ready-to-paste setup prompt (pre-filled with your project reference) for Claude Code, Cursor, or any coding agent. | ||
| The `init` scaffold now imports from `@trigger.dev/sdk` instead of the deprecated `@trigger.dev/sdk/v3` subpath. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| import { createContext, useContext, useState } from "react"; | ||
| import { CheckIcon, SparklesIcon } from "@heroicons/react/20/solid"; | ||
| import { createContext, useContext, useRef, useState } from "react"; | ||
| import { useAppOrigin } from "~/hooks/useAppOrigin"; | ||
| import { useProject } from "~/hooks/useProject"; | ||
| import { useTriggerCliTag } from "~/hooks/useTriggerCliTag"; | ||
| import { Button } from "./primitives/Buttons"; | ||
| import { | ||
| ClientTabs, | ||
| ClientTabsContent, | ||
| @@ -10,6 +12,7 @@ import { | ||
| } from "./primitives/ClientTabs"; | ||
| import { ClipboardField } from "./primitives/ClipboardField"; | ||
| import { Header3 } from "./primitives/Headers"; | ||
| import { SimpleTooltip } from "./primitives/Tooltip"; | ||
| type PackageManagerContextType = { | ||
| activePackageManager: string; | ||
| @@ -36,26 +39,23 @@ function usePackageManager() { | ||
| return context; | ||
| } | ||
| function getApiUrlArg() { | ||
| function useApiUrl() { | ||
| const appOrigin = useAppOrigin(); | ||
| let apiUrl: string | undefined = undefined; | ||
| switch (appOrigin) { | ||
| case "https://cloud.trigger.dev": | ||
| // don't display the arg, use the CLI default | ||
| break; | ||
| return undefined; | ||
| case "https://test-cloud.trigger.dev": | ||
| apiUrl = "https://test-api.trigger.dev"; | ||
| break; | ||
| return "https://test-api.trigger.dev"; | ||
| case "https://internal.trigger.dev": | ||
| apiUrl = "https://internal-api.trigger.dev"; | ||
| break; | ||
| return "https://internal-api.trigger.dev"; | ||
| default: | ||
| apiUrl = appOrigin; | ||
| break; | ||
| return appOrigin; | ||
| } | ||
| } | ||
| function getApiUrlArg() { | ||
| const apiUrl = useApiUrl(); | ||
| return apiUrl ? `-a ${apiUrl}` : undefined; | ||
| } | ||
| @@ -117,6 +117,86 @@ export function InitCommandV3({ title }: TabsProps) { | ||
| ); | ||
| } | ||
| function buildAgentSetupPrompt({ | ||
| projectRef, | ||
| apiUrl, | ||
| cliTag, | ||
| }: { | ||
| projectRef: string; | ||
| apiUrl: string | undefined; | ||
| cliTag: string; | ||
| }) { | ||
| const apiUrlArg = apiUrl ? ` -a ${apiUrl}` : ""; | ||
| const apiUrlLine = apiUrl ? `\nTrigger.dev API URL: ${apiUrl}` : ""; | ||
| return `Set up Trigger.dev in this project. | ||
| Trigger.dev runs your background tasks. This is an existing codebase — add Trigger.dev to it and get one task running in the development environment. | ||
| Project reference: ${projectRef}${apiUrlLine} | ||
| How to do it: | ||
| 1. If you have the Trigger.dev MCP server available, use its "initialize_project" tool with the project reference above. | ||
| 2. Otherwise run this and follow its output: | ||
| npx trigger.dev@${cliTag} init -p ${projectRef}${apiUrlArg} | ||
| 3. If you set it up by hand, follow https://trigger.dev/docs/manual-setup and make sure you end up with: | ||
| - "@trigger.dev/sdk" installed (latest) and "@trigger.dev/build" as a dev dependency | ||
| - a trigger.config.ts with: import { defineConfig } from "@trigger.dev/sdk", project: "${projectRef}", dirs: ["./src/trigger"], and a maxDuration | ||
| - a src/trigger/ directory with at least one exported task created with task() from "@trigger.dev/sdk" | ||
| - trigger.config.ts added to tsconfig "include", and ".trigger" added to .gitignore | ||
| Golden rules: | ||
| - Import from "@trigger.dev/sdk". Never "@trigger.dev/sdk/v3" or the deprecated client.defineJob. | ||
| - Export every task, including subtasks. | ||
| - Use the built-in fetch, not node-fetch. | ||
| - Never wrap wait.*, triggerAndWait, or batchTriggerAndWait in Promise.all. | ||
| Two steps I have to do myself — ask me when you need them: | ||
| - Running "npx trigger.dev@${cliTag} login" (it opens a browser). | ||
| - Giving you the development TRIGGER_SECRET_KEY from the dashboard to put in .env. | ||
| When you're done, run "npx trigger.dev@${cliTag} dev" and confirm the task shows up in the Trigger.dev dashboard.`; | ||
| } | ||
| export function InitAgentPromptV3() { | ||
| const project = useProject(); | ||
| const apiUrl = useApiUrl(); | ||
| const cliTag = useTriggerCliTag(); | ||
| const [copied, setCopied] = useState(false); | ||
| const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
| const onCopy = () => { | ||
| const prompt = buildAgentSetupPrompt({ | ||
| projectRef: project.externalRef, | ||
| apiUrl, | ||
| cliTag, | ||
| }); | ||
| setCopied(true); | ||
| if (timeoutRef.current) clearTimeout(timeoutRef.current); | ||
| timeoutRef.current = setTimeout(() => setCopied(false), 2000); | ||
| void navigator.clipboard.writeText(prompt).catch(() => {}); | ||
| }; | ||
| return ( | ||
| <SimpleTooltip | ||
| asChild | ||
| tabbable | ||
| button={ | ||
| <Button | ||
| type="button" | ||
| variant="primary/medium" | ||
| LeadingIcon={copied ? CheckIcon : SparklesIcon} | ||
| leadingIconClassName={copied ? "text-success" : undefined} | ||
| onClick={onCopy} | ||
| > | ||
| {copied ? "Copied prompt" : "Copy AI agent prompt"} | ||
| </Button> | ||
| } | ||
| content="Copies a setup prompt to paste into Claude Code, Cursor, or any coding agent" | ||
| /> | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ); | ||
| } | ||
| export function TriggerDevStepV3({ title }: TabsProps) { | ||
| const triggerCliTag = useTriggerCliTag(); | ||
| const { activePackageManager, setActivePackageManager } = usePackageManager(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -71,6 +71,7 @@ import { useOrganization } from "~/hooks/useOrganizations"; | ||
| import { useProject } from "~/hooks/useProject"; | ||
| import { useSearchParams } from "~/hooks/useSearchParam"; | ||
| import { useShortcutKeys } from "~/hooks/useShortcutKeys"; | ||
| import { prisma } from "~/db.server"; | ||
| import { findProjectBySlug } from "~/models/project.server"; | ||
| import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server"; | ||
| import { | ||
| @@ -128,7 +129,18 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { | ||
| const usefulLinksPreference = await getUsefulLinksPreference(request); | ||
| return typeddefer({ items, hourlyActivity, runningStates, usefulLinksPreference }); | ||
| const initialized = await prisma.project.findFirst({ | ||
| where: { id: project.id }, | ||
| select: { initializedAt: true }, | ||
| }); | ||
| return typeddefer({ | ||
| items, | ||
| hourlyActivity, | ||
| runningStates, | ||
| usefulLinksPreference, | ||
| projectInitializedAt: initialized?.initializedAt ?? null, | ||
| }); | ||
ericallam marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } catch (error) { | ||
| console.error(error); | ||
| throw new Response(undefined, { | ||
| @@ -195,7 +207,7 @@ export default function Page() { | ||
| const organization = useOrganization(); | ||
| const project = useProject(); | ||
| const environment = useEnvironment(); | ||
| const { items, hourlyActivity, runningStates, usefulLinksPreference } = | ||
| const { items, hourlyActivity, runningStates, usefulLinksPreference, projectInitializedAt } = | ||
| useTypedLoaderData<typeof loader>(); | ||
| const { value, values } = useSearchParams(); | ||
| @@ -355,7 +367,7 @@ export default function Page() { | ||
| </div> | ||
| ) : environment.type === "DEVELOPMENT" ? ( | ||
| <MainCenteredContainer className="max-w-prose"> | ||
| <HasNoTasksDev /> | ||
| <HasNoTasksDev initializedAt={projectInitializedAt} /> | ||
| </MainCenteredContainer> | ||
| ) : ( | ||
| <MainCenteredContainer className="max-w-prose"> | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.