Compare commits

...

22 Commits

Author SHA1 Message Date
nicktrn 309608b79b fix v3 setup command again
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-infra (push) Has been skipped
2024-06-05 15:02:34 +01:00
nicktrn 3314169af4 enable v3 projects by default on self-hosted instances 2024-06-05 14:52:56 +01:00
nicktrn 81ca34a847 checkpoint test is docker specific 2024-06-05 13:27:30 +01:00
nicktrn 0b9c6ee45c Merge branch 'main' into v3/self-hosting 2024-06-05 13:14:02 +01:00
nicktrn b5365737a4 fix var subst
🚀 Publish Trigger.dev Docker / units (push) Failing after 1s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-infra (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
2024-04-28 20:20:18 +01:00
nicktrn 43b0d30455 add docker provider to build matrix 2024-04-28 20:17:07 +01:00
nicktrn a625b8ee27 remove concurrency group from infra workflow 2024-04-28 20:11:39 +01:00
nicktrn 204397316d lockfile 2024-04-28 19:50:05 +01:00
nicktrn 8bdcaaef09 add infra image build to default publish workflow 2024-04-28 19:45:03 +01:00
nicktrn e9c306cfa3 Merge branch 'main' into v3/self-hosting 2024-04-28 18:57:46 +01:00
nicktrn 0bbcb8241b bump remaining containers to node 20 2024-04-26 23:58:52 +01:00
nicktrn 16b707f49d update docker provider containerfile 2024-04-26 23:33:25 +01:00
nicktrn 3efdf7cada add docker to coordinator image 2024-04-26 23:32:59 +01:00
nicktrn a3b884b578 better checkpoint support check for coordinator 2024-04-26 23:31:26 +01:00
nicktrn be083bdba4 add public init to providers 2024-04-26 22:50:00 +01:00
nicktrn dad56aa791 control forced checkpoint simulation via env var 2024-04-26 22:37:37 +01:00
nicktrn 29b3961050 improve checkpoint support detection 2024-04-26 22:37:20 +01:00
nicktrn 4c3076bd9d if specified, add api url to dev command shown after init 2024-04-26 18:01:10 +01:00
nicktrn 518cd98ad4 pin sdk version during init 2024-04-26 18:00:20 +01:00
nicktrn 8330f404ec shared env var to disable telemetry in cli and webapp 2024-04-26 17:59:35 +01:00
nicktrn ca97d89566 fix displayed init command for self-hosted setups 2024-04-26 17:57:10 +01:00
nicktrn d0bb06a4d0 add amin email regex env var 2024-04-26 17:55:28 +01:00
22 changed files with 338 additions and 141 deletions
+2
View File
@@ -25,6 +25,8 @@ DEV_OTEL_BATCH_PROCESSING_ENABLED="0"
# OPTIONAL VARIABLES
# This is used for validating emails that are allowed to log in. Every email that do not match this regex will be rejected.
# WHITELISTED_EMAILS="authorized@yahoo\.com|authorized@gmail\.com"
# Accounts with these emails will get global admin rights. This grants access to the admin UI.
# ADMIN_EMAILS="admin@example\.com|another-admin@example\.com"
# This is used for logging in via GitHub. You can leave these commented out if you don't want to use GitHub for authentication.
# AUTH_GITHUB_CLIENT_ID=
# AUTH_GITHUB_CLIENT_SECRET=
+29 -11
View File
@@ -1,6 +1,7 @@
name: "🚢 Publish Infra Images"
on:
workflow_call:
push:
tags:
- "infra-dev-*"
@@ -29,9 +30,6 @@ permissions:
packages: write
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
env:
AWS_REGION: us-east-1
@@ -39,7 +37,7 @@ jobs:
build:
strategy:
matrix:
package: [coordinator, kubernetes-provider]
package: [coordinator, docker-provider, kubernetes-provider]
runs-on: buildjet-16vcpu-ubuntu-2204
env:
DOCKER_BUILDKIT: "1"
@@ -48,20 +46,40 @@ jobs:
- name: Generate image reference
id: prep
# WARNING: This step expects the workflow to have been triggered by a specific tag format of: infra-${env}-*
run: |
env=$(echo ${{ github.ref_name }} | cut -d- -f2)
sha=${GITHUB_SHA::7}
ts=$(date +%s)
# set image repo
if [[ "${{ matrix.package }}" == *-provider ]]; then
provider_type=$(echo ${{ matrix.package }} | cut -d- -f1)
provider_type=$(echo "${{ matrix.package }}" | cut -d- -f1)
repository=provider/${provider_type}
else
repository=${{ matrix.package }}
repository="${{ matrix.package }}"
fi
echo "IMAGE_TAG=${env}-${sha}-${ts}" >> "$GITHUB_OUTPUT"
echo "REPOSITORY=${repository}" >> "$GITHUB_OUTPUT"
# set image tag
if [[ "${{ github.ref_type }}" == "tag" ]]; then
if [[ "${{ github.ref_name }}" == infra-*-* ]]; then
env=$(echo ${{ github.ref_name }} | cut -d- -f2)
sha=${${{ github.sha }}::7}
ts=$(date +%s)
image_tag=${env}-${sha}-${ts}
elif [[ "${{ github.ref_name }}" == v.docker.* ]]; then
version="${GITHUB_REF_NAME#v.docker.}"
image_tag="v${version}"
elif [[ "${{ github.ref_name }}" == build-* ]]; then
image_tag="${GITHUB_REF_NAME#build-}"
else
echo "Invalid tag: ${{ github.ref_name }}"
exit 1
fi
elif [[ "${{ github.ref_name }}" == "main" ]]; then
image_tag="main"
else
echo "Invalid reference: ${{ github.ref }}"
exit 1
fi
echo "IMAGE_TAG=${image_tag}" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
+5
View File
@@ -57,3 +57,8 @@ jobs:
needs: [typecheck, units]
uses: ./.github/workflows/publish-docker.yml
secrets: inherit
publish-infra:
needs: [typecheck, units]
uses: ./.github/workflows/publish-infra.yml
secrets: inherit
+4 -4
View File
@@ -1,19 +1,19 @@
# syntax=docker/dockerfile:labs
FROM node:18-bullseye-slim@sha256:a4edd54dcfdcacc8a4100fee71498e8671d99556a1acf5614539214a70092426 AS node-18
FROM node:20-bookworm-slim@sha256:72f2f046a5f8468db28730b990b37de63ce93fd1a72a40f531d6aa82afdf0d46 AS node-20
WORKDIR /app
FROM node-18 AS pruner
FROM node-20 AS pruner
COPY --chown=node:node . .
RUN npx -q turbo@1.10.9 prune --scope=coordinator --docker
RUN find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
FROM node-18 AS base
FROM node-20 AS base
RUN apt-get update \
&& apt-get install -y buildah ca-certificates dumb-init \
&& apt-get install -y buildah ca-certificates dumb-init docker.io \
&& rm -rf /var/lib/apt/lists/*
COPY --chown=node:node .gitignore .gitignore
+39 -46
View File
@@ -12,7 +12,12 @@ import {
} from "@trigger.dev/core/v3";
import { ZodNamespace } from "@trigger.dev/core/v3/zodNamespace";
import { ZodSocketConnection } from "@trigger.dev/core/v3/zodSocket";
import { HttpReply, getTextBody, SimpleLogger } from "@trigger.dev/core-apps";
import {
HttpReply,
getTextBody,
SimpleLogger,
testDockerCheckpoint,
} from "@trigger.dev/core-apps";
import { ExponentialBackoff } from "./backoff";
import { collectDefaultMetrics, register, Gauge } from "prom-client";
@@ -125,65 +130,53 @@ class Checkpointer {
constructor(private opts = { forceSimulate: false }) {}
async initialize(): Promise<CheckpointerInitializeReturn> {
async init(): Promise<CheckpointerInitializeReturn> {
if (this.#initialized) {
return this.#getInitializeReturn();
return this.#getInitReturn(this.#canCheckpoint);
}
this.#logger.log(`${this.#dockerMode ? "Docker" : "Kubernetes"} mode`);
if (this.#dockerMode) {
try {
await $`criu --version`;
} catch (error) {
this.#logger.error("No checkpoint support: Missing CRIU binary");
this.#logger.error("Will simulate instead");
this.#canCheckpoint = false;
this.#initialized = true;
const testCheckpoint = await testDockerCheckpoint();
return this.#getInitializeReturn();
if (testCheckpoint.ok) {
return this.#getInitReturn(true);
}
try {
await $`docker checkpoint`;
} catch (error) {
this.#logger.error(
"No checkpoint support: Docker needs to have experimental features enabled"
);
this.#logger.error("Will simulate instead");
this.#canCheckpoint = false;
this.#initialized = true;
return this.#getInitializeReturn();
}
this.#logger.error(testCheckpoint.message, testCheckpoint.error ?? "");
return this.#getInitReturn(false);
} else {
try {
await $`buildah login --get-login ${REGISTRY_HOST}`;
} catch (error) {
this.#logger.error(`No checkpoint support: Not logged in to registry ${REGISTRY_HOST}`);
this.#canCheckpoint = false;
this.#initialized = true;
return this.#getInitializeReturn();
return this.#getInitReturn(false);
}
}
this.#logger.log(
`Full checkpoint support${
this.#dockerMode && this.opts.forceSimulate ? " with forced simulation enabled." : "!"
}`
);
this.#initialized = true;
this.#canCheckpoint = true;
return this.#getInitializeReturn();
return this.#getInitReturn(true);
}
#getInitializeReturn(): CheckpointerInitializeReturn {
#getInitReturn(canCheckpoint: boolean): CheckpointerInitializeReturn {
this.#initialized = true;
this.#canCheckpoint = canCheckpoint;
if (canCheckpoint) {
this.#logger.log("Full checkpoint support!");
}
const willSimulate = this.#dockerMode && (!this.#canCheckpoint || this.opts.forceSimulate);
if (willSimulate) {
this.#logger.log("Simulation mode enabled. Containers will be paused, not checkpointed.", {
forceSimulate: this.opts.forceSimulate,
});
}
return {
canCheckpoint: this.#canCheckpoint,
willSimulate: this.#dockerMode && (!this.#canCheckpoint || this.opts.forceSimulate),
canCheckpoint,
willSimulate,
};
}
@@ -355,7 +348,7 @@ class Checkpointer {
projectRef,
deploymentVersion,
}: CheckpointAndPushOptions): Promise<CheckpointAndPushResult> {
await this.initialize();
await this.init();
const options = {
runId,
@@ -617,7 +610,7 @@ class TaskCoordinator {
private host = "0.0.0.0"
) {
this.#httpServer = this.#createHttpServer();
this.#checkpointer.initialize();
this.#checkpointer.init();
this.#delayThresholdInMs = this.#getDelayThreshold();
if (process.env.DELAY_THRESHOLD_IN_MS) {
@@ -1034,7 +1027,7 @@ class TaskCoordinator {
return;
}
const { canCheckpoint, willSimulate } = await this.#checkpointer.initialize();
const { canCheckpoint, willSimulate } = await this.#checkpointer.init();
const willCheckpointAndRestore = canCheckpoint || willSimulate;
@@ -1131,7 +1124,7 @@ class TaskCoordinator {
return;
}
const { canCheckpoint, willSimulate } = await this.#checkpointer.initialize();
const { canCheckpoint, willSimulate } = await this.#checkpointer.init();
const willCheckpointAndRestore = canCheckpoint || willSimulate;
@@ -1185,7 +1178,7 @@ class TaskCoordinator {
socket.on("WAIT_FOR_TASK", async (message, callback) => {
logger.log("[WAIT_FOR_TASK]", message);
const { canCheckpoint, willSimulate } = await this.#checkpointer.initialize();
const { canCheckpoint, willSimulate } = await this.#checkpointer.init();
const willCheckpointAndRestore = canCheckpoint || willSimulate;
@@ -1227,7 +1220,7 @@ class TaskCoordinator {
socket.on("WAIT_FOR_BATCH", async (message, callback) => {
logger.log("[WAIT_FOR_BATCH]", message);
const { canCheckpoint, willSimulate } = await this.#checkpointer.initialize();
const { canCheckpoint, willSimulate } = await this.#checkpointer.init();
const willCheckpointAndRestore = canCheckpoint || willSimulate;
+41 -10
View File
@@ -1,16 +1,47 @@
# syntax=docker/dockerfile:labs
FROM node:18-slim AS base
RUN apt-get update \
&& apt-get install -y dumb-init
FROM base
FROM node:20-alpine@sha256:7a91aa397f2e2dfbfcdad2e2d72599f374e0b0172be1d86eeb73f1d33f36a4b2 AS node-20-alpine
WORKDIR /app
COPY --chown=node dist/index.mjs /app/
FROM node-20-alpine AS pruner
COPY --chown=node:node . .
RUN npx -q turbo@1.10.9 prune --scope=docker-provider --docker
RUN find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
FROM node-20-alpine AS base
RUN apk add --no-cache dumb-init docker
COPY --chown=node:node .gitignore .gitignore
COPY --from=pruner --chown=node:node /app/out/json/ .
COPY --from=pruner --chown=node:node /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=pruner --chown=node:node /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
FROM base AS dev-deps
RUN corepack enable
ENV NODE_ENV development
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --no-frozen-lockfile
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --ignore-scripts --no-frozen-lockfile
FROM base AS builder
RUN corepack enable
COPY --from=pruner --chown=node:node /app/out/full/ .
COPY --from=dev-deps --chown=node:node /app/ .
COPY --chown=node:node turbo.json turbo.json
RUN pnpm run -r --filter docker-provider build:bundle
FROM base AS runner
RUN corepack enable
ENV NODE_ENV production
COPY --from=builder --chown=node:node /app/apps/docker-provider/dist/index.mjs ./index.mjs
EXPOSE 8000
ENTRYPOINT [ "/usr/bin/dumb-init", "--", "/usr/local/bin/node", "/app/index.mjs" ]
USER node
CMD [ "/usr/bin/dumb-init", "--", "/usr/local/bin/node", "./index.mjs" ]
+34 -44
View File
@@ -6,6 +6,8 @@ import {
TaskOperationsRestoreOptions,
TaskOperationsCreateOptions,
TaskOperationsIndexOptions,
isExecaChildProcess,
testDockerCheckpoint,
} from "@trigger.dev/core-apps";
import { setTimeout } from "node:timers/promises";
import { PostStartCauses, PreStopCauses } from "@trigger.dev/core/v3";
@@ -23,70 +25,58 @@ const FORCE_CHECKPOINT_SIMULATION = ["1", "true"].includes(
const logger = new SimpleLogger(`[${MACHINE_NAME}]`);
type InitializeReturn = {
type TaskOperationsInitReturn = {
canCheckpoint: boolean;
willSimulate: boolean;
};
function isExecaChildProcess(maybeExeca: unknown): maybeExeca is Awaited<ExecaChildProcess> {
return typeof maybeExeca === "object" && maybeExeca !== null && "escapedCommand" in maybeExeca;
}
class DockerTaskOperations implements TaskOperations {
#initialized = false;
#canCheckpoint = false;
constructor(private opts = { forceSimulate: false }) {}
async #initialize(): Promise<InitializeReturn> {
async init(): Promise<TaskOperationsInitReturn> {
if (this.#initialized) {
return this.#getInitializeReturn();
return this.#getInitReturn(this.#canCheckpoint);
}
logger.log("Initializing task operations");
if (this.opts.forceSimulate) {
logger.log("Forced simulation enabled. Will simulate regardless of checkpoint support.");
const testCheckpoint = await testDockerCheckpoint();
if (testCheckpoint.ok) {
return this.#getInitReturn(true);
}
try {
await $`criu --version`;
} catch (error) {
logger.error("No checkpoint support: Missing CRIU binary. Will simulate instead.");
this.#canCheckpoint = false;
this.#initialized = true;
return this.#getInitializeReturn();
}
try {
await $`docker checkpoint`;
} catch (error) {
logger.error("No checkpoint support: Docker needs to have experimental features enabled");
logger.error("Will simulate instead");
this.#canCheckpoint = false;
this.#initialized = true;
return this.#getInitializeReturn();
}
logger.log("Full checkpoint support!");
this.#initialized = true;
this.#canCheckpoint = true;
return this.#getInitializeReturn();
logger.error(testCheckpoint.message, testCheckpoint.error);
return this.#getInitReturn(false);
}
#getInitializeReturn(): InitializeReturn {
#getInitReturn(canCheckpoint: boolean): TaskOperationsInitReturn {
this.#initialized = true;
this.#canCheckpoint = canCheckpoint;
if (canCheckpoint) {
logger.log("Full checkpoint support!");
}
const willSimulate = !canCheckpoint || this.opts.forceSimulate;
if (willSimulate) {
logger.log("Simulation mode enabled. Containers will be paused, not checkpointed.", {
forceSimulate: this.opts.forceSimulate,
});
}
return {
canCheckpoint: this.#canCheckpoint,
willSimulate: !this.#canCheckpoint || this.opts.forceSimulate,
canCheckpoint,
willSimulate,
};
}
async index(opts: TaskOperationsIndexOptions) {
await this.#initialize();
await this.init();
const containerName = this.#getIndexContainerName(opts.shortCode);
@@ -129,7 +119,7 @@ class DockerTaskOperations implements TaskOperations {
}
async create(opts: TaskOperationsCreateOptions) {
await this.#initialize();
await this.init();
const containerName = this.#getRunContainerName(opts.runId);
@@ -165,7 +155,7 @@ class DockerTaskOperations implements TaskOperations {
}
async restore(opts: TaskOperationsRestoreOptions) {
await this.#initialize();
await this.init();
const containerName = this.#getRunContainerName(opts.runId);
@@ -194,7 +184,7 @@ class DockerTaskOperations implements TaskOperations {
}
async delete(opts: { runId: string }) {
await this.#initialize();
await this.init();
const containerName = this.#getRunContainerName(opts.runId);
await this.#sendPreStop(containerName);
@@ -203,7 +193,7 @@ class DockerTaskOperations implements TaskOperations {
}
async get(opts: { runId: string }) {
await this.#initialize();
await this.init();
logger.log("noop: get");
}
+3 -3
View File
@@ -1,14 +1,14 @@
FROM node:18-alpine@sha256:ca9f6cb0466f9638e59e0c249d335a07c867cd50c429b5c7830dda1bed584649 AS node-18-alpine
FROM node:20-alpine@sha256:7a91aa397f2e2dfbfcdad2e2d72599f374e0b0172be1d86eeb73f1d33f36a4b2 AS node-20-alpine
WORKDIR /app
FROM node-18-alpine AS pruner
FROM node-20-alpine AS pruner
COPY --chown=node:node . .
RUN npx -q turbo@1.10.9 prune --scope=kubernetes-provider --docker
RUN find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
FROM node-18-alpine AS base
FROM node-20-alpine AS base
RUN apk add --no-cache dumb-init
+4
View File
@@ -47,6 +47,10 @@ class KubernetesTaskOperations implements TaskOperations {
this.#k8sApi = this.#createK8sApi();
}
async init() {
// noop
}
async index(opts: TaskOperationsIndexOptions) {
await this.#createJob(
{
+33 -3
View File
@@ -8,6 +8,7 @@ import {
} from "./primitives/ClientTabs";
import { ClipboardField } from "./primitives/ClipboardField";
import { Paragraph } from "./primitives/Paragraph";
import { useAppOrigin } from "~/hooks/useAppOrigin";
export function InitCommand({ appOrigin, apiKey }: { appOrigin: string; apiKey: string }) {
return (
@@ -133,9 +134,38 @@ export function TriggerDevStep({ extra }: { extra?: string }) {
// Trigger.dev version 3 setup commands
const v3PackageTag = "beta";
function getApiUrlArg() {
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;
case "https://test-cloud.trigger.dev":
apiUrl = "https://test-api.trigger.dev";
break;
case "https://internal.trigger.dev":
apiUrl = "https://internal-api.trigger.dev";
break;
default:
apiUrl = appOrigin;
break;
}
return apiUrl ? `-a ${apiUrl}` : undefined;
}
export function InitCommandV3() {
const project = useProject();
const projectRef = project.ref;
const apiUrlArg = getApiUrlArg();
const initCommandParts = [`trigger.dev@${v3PackageTag}`, "init", `-p ${projectRef}`, apiUrlArg];
const initCommand = initCommandParts.filter(Boolean).join(" ");
return (
<ClientTabs defaultValue="npm">
<ClientTabsList>
@@ -148,7 +178,7 @@ export function InitCommandV3() {
variant="primary/medium"
iconButton
className="mb-4"
value={`npx trigger.dev@${v3PackageTag} init -p ${projectRef}`}
value={`npx ${initCommand}`}
/>
</ClientTabsContent>
<ClientTabsContent value={"pnpm"}>
@@ -156,7 +186,7 @@ export function InitCommandV3() {
variant="primary/medium"
iconButton
className="mb-4"
value={`pnpm dlx trigger.dev@${v3PackageTag} init -p ${projectRef}`}
value={`pnpm dlx ${initCommand}`}
/>
</ClientTabsContent>
<ClientTabsContent value={"yarn"}>
@@ -164,7 +194,7 @@ export function InitCommandV3() {
variant="primary/medium"
iconButton
className="mb-4"
value={`yarn dlx trigger.dev@${v3PackageTag} init -p ${projectRef}`}
value={`yarn dlx ${initCommand}`}
/>
</ClientTabsContent>
</ClientTabs>
+2
View File
@@ -27,6 +27,7 @@ const EnvironmentSchema = z.object({
.string()
.refine(isValidRegex, "WHITELISTED_EMAILS must be a valid regex.")
.optional(),
ADMIN_EMAILS: z.string().refine(isValidRegex, "ADMIN_EMAILS must be a valid regex.").optional(),
REMIX_APP_PORT: z.string().optional(),
LOGIN_ORIGIN: z.string().default("http://localhost:3030"),
APP_ORIGIN: z.string().default("http://localhost:3030"),
@@ -36,6 +37,7 @@ const EnvironmentSchema = z.object({
POSTHOG_PROJECT_KEY: z.string().optional(),
TELEMETRY_TRIGGER_API_KEY: z.string().optional(),
TELEMETRY_TRIGGER_API_URL: z.string().optional(),
TRIGGER_TELEMETRY_DISABLED: z.string().optional(),
HIGHLIGHT_PROJECT_ID: z.string().optional(),
AUTH_GITHUB_CLIENT_ID: z.string().optional(),
AUTH_GITHUB_CLIENT_SECRET: z.string().optional(),
+22 -11
View File
@@ -7,20 +7,31 @@ export type TriggerFeatures = {
alertsEnabled: boolean;
};
// If the request host is cloud.trigger.dev then we are on the managed cloud
// or if env.NODE_ENV is development
export function featuresForRequest(request: Request): TriggerFeatures {
const url = requestUrl(request);
const isManagedCloud =
url.host === "cloud.trigger.dev" ||
url.host === "test-cloud.trigger.dev" ||
url.host === "internal.trigger.dev" ||
process.env.CLOUD_ENV === "development";
function isManagedCloud(host: string): boolean {
return (
host === "cloud.trigger.dev" ||
host === "test-cloud.trigger.dev" ||
host === "internal.trigger.dev" ||
process.env.CLOUD_ENV === "development"
);
}
export function featuresForHost(host: string): TriggerFeatures {
return {
isManagedCloud,
isManagedCloud: isManagedCloud(host),
v3Enabled: env.V3_ENABLED === "true",
alertsEnabled: env.ALERT_FROM_EMAIL !== undefined && env.ALERT_RESEND_API_KEY !== undefined,
};
}
export function featuresForRequest(request: Request): TriggerFeatures {
const url = requestUrl(request);
return featuresForHost(url.host);
}
export function featuresForUrl(urlString: string): TriggerFeatures {
const url = new URL(urlString);
return featuresForHost(url.host);
}
@@ -12,6 +12,7 @@ import { createProject } from "./project.server";
import { generate } from "random-words";
import { createApiKeyForEnv, createPkApiKeyForEnv, envSlug } from "./api-key.server";
import { env } from "~/env.server";
import { featuresForHost } from "~/features.server";
export type { Organization };
@@ -52,6 +53,8 @@ export async function createOrganization(
);
}
const features = featuresForHost(env.APP_ORIGIN);
const organization = await prisma.organization.create({
data: {
title,
@@ -64,6 +67,7 @@ export async function createOrganization(
role: "ADMIN",
},
},
v3Enabled: features.v3Enabled && !features.isManagedCloud,
},
include: {
members: true,
+11 -2
View File
@@ -47,12 +47,21 @@ export async function findOrCreateMagicLinkUser(
},
});
const adminEmailRegex = env.ADMIN_EMAILS ? new RegExp(env.ADMIN_EMAILS) : undefined;
const makeAdmin = adminEmailRegex ? adminEmailRegex.test(input.email) : false;
const user = await prisma.user.upsert({
where: {
email: input.email,
},
update: { email: input.email },
create: { email: input.email, authenticationMethod: "MAGIC_LINK" },
update: {
email: input.email,
},
create: {
email: input.email,
authenticationMethod: "MAGIC_LINK",
admin: makeAdmin, // only on create, to prevent automatically removing existing admins
},
});
return {
@@ -22,6 +22,11 @@ class Telemetry {
#triggerClient: TriggerClient | undefined = undefined;
constructor({ postHogApiKey, trigger }: Options) {
if (env.TRIGGER_TELEMETRY_DISABLED !== undefined) {
console.log("📉 Telemetry disabled");
return;
}
if (postHogApiKey) {
this.#posthogClient = new PostHog(postHogApiKey, { host: "https://eu.posthog.com" });
} else {
+10 -3
View File
@@ -30,6 +30,8 @@ import { logger } from "../utilities/logger";
import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { login } from "./login";
import { spinner } from "../utilities/windows";
import { CLOUD_API_URL } from "../consts";
import * as packageJson from "../../package.json";
const InitCommandOptions = CommonCommandOptions.extend({
projectRef: z.string().optional(),
@@ -53,7 +55,7 @@ export function configureInitCommand(program: Command) {
.option(
"-t, --tag <package tag>",
"The version of the @trigger.dev/sdk package to install",
"beta"
packageJson.version
)
.option("--skip-package-install", "Skip installing the @trigger.dev/sdk package")
.option("--override-config", "Override the existing config file if it exists")
@@ -160,7 +162,9 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
log.info("Next steps:");
log.info(
` 1. To start developing, run ${chalk.green(
`npx trigger.dev@${options.tag} dev`
`npx trigger.dev@${options.tag} dev${
options.apiUrl === CLOUD_API_URL ? "" : ` -a ${options.apiUrl}`
}`
)} in your project directory`
);
log.info(` 2. Visit your ${projectDashboard} to view your newly created tasks.`);
@@ -401,7 +405,8 @@ async function installPackages(dir: string, options: InitCommandOptions) {
case "npm": {
installSpinner.start(`Running npm install @trigger.dev/sdk@${options.tag}`);
await execa("npm", ["install", `@trigger.dev/sdk@${options.tag}`], {
// --save-exact: pin version, e.g. 3.0.0-beta.20 instead of ^3.0.0-beta.20
await execa("npm", ["install", "--save-exact", `@trigger.dev/sdk@${options.tag}`], {
cwd: projectDir,
stdio: options.logLevel === "debug" ? "inherit" : "ignore",
});
@@ -411,6 +416,7 @@ async function installPackages(dir: string, options: InitCommandOptions) {
case "pnpm": {
installSpinner.start(`Running pnpm add @trigger.dev/sdk@${options.tag}`);
// pins version by default
await execa("pnpm", ["add", `@trigger.dev/sdk@${options.tag}`], {
cwd: projectDir,
stdio: options.logLevel === "debug" ? "inherit" : "ignore",
@@ -421,6 +427,7 @@ async function installPackages(dir: string, options: InitCommandOptions) {
case "yarn": {
installSpinner.start(`Running yarn add @trigger.dev/sdk@${options.tag}`);
// pins version by default
await execa("yarn", ["add", `@trigger.dev/sdk@${options.tag}`], {
cwd: projectDir,
stdio: options.logLevel === "debug" ? "inherit" : "ignore",
+7 -1
View File
@@ -9,9 +9,15 @@ import {
SEMRESATTRS_SERVICE_NAME,
SEMRESATTRS_SERVICE_VERSION,
} from "@opentelemetry/semantic-conventions";
import { logger } from "../utilities/logger";
function initializeTracing(): NodeTracerProvider | undefined {
if (process.argv.includes("--skip-telemetry") || process.env.TRIGGER_DEV_SKIP_TELEMETRY) {
if (
process.argv.includes("--skip-telemetry") ||
process.env.TRIGGER_DEV_SKIP_TELEMETRY || // only for backwards compat
process.env.TRIGGER_TELEMETRY_DISABLED
) {
logger.debug("📉 Telemetry disabled");
return;
}
+5 -2
View File
@@ -28,10 +28,13 @@
"@trigger.dev/core": "workspace:*",
"@trigger.dev/tsconfig": "workspace:*",
"@types/node": "18",
"typescript": "^5.3.0",
"socket.io-client": "^4.7.4"
"socket.io-client": "^4.7.4",
"typescript": "^5.3.0"
},
"engines": {
"node": ">=18.0.0"
},
"dependencies": {
"execa": "^8.0.1"
}
}
+67
View File
@@ -0,0 +1,67 @@
import { randomUUID } from "crypto";
import { $, type ExecaChildProcess } from "execa";
export function isExecaChildProcess(maybeExeca: unknown): maybeExeca is Awaited<ExecaChildProcess> {
return typeof maybeExeca === "object" && maybeExeca !== null && "escapedCommand" in maybeExeca;
}
export type CheckpointTestResult =
| {
ok: true;
}
| {
ok: false;
message: string;
error?: unknown;
};
export async function testDockerCheckpoint(): Promise<CheckpointTestResult> {
try {
// Create a dummy container
const container =
await $`docker run -d --rm --name init-dummy-${randomUUID()} docker.io/library/busybox sleep 10`;
// Checkpoint it
await $`docker checkpoint create ${container} init-check`;
} catch (error) {
if (!isExecaChildProcess(error)) {
return {
ok: false,
message: "No checkpoint support: Unknown error.",
error,
};
}
if (error.stderr.includes("criu")) {
if (error.stderr.includes("executable file not found")) {
return {
ok: false,
message: "No checkpoint support: Missing CRIU binary.",
};
}
return {
ok: false,
message: "No checkpoint support: Unknown CRIU error.",
error,
};
}
if (error.stderr.includes("experimental features enabled")) {
return {
ok: false,
message: "No checkpoint support: Please enable docker experimental features.",
};
}
return {
ok: false,
message: "No checkpoint support: Unknown execa error.",
error,
};
}
return {
ok: true,
};
}
+1
View File
@@ -1,3 +1,4 @@
export * from "./http";
export * from "./logger";
export * from "./provider";
export * from "./checkpoints";
+6 -1
View File
@@ -62,10 +62,14 @@ export interface TaskOperationsRestoreOptions {
}
export interface TaskOperations {
init: () => Promise<any>;
// CRUD
index: (opts: TaskOperationsIndexOptions) => Promise<any>;
create: (opts: TaskOperationsCreateOptions) => Promise<any>;
restore: (opts: TaskOperationsRestoreOptions) => Promise<any>;
// unimplemented
delete: (...args: any[]) => Promise<any>;
get: (...args: any[]) => Promise<any>;
}
@@ -303,7 +307,8 @@ export class ProviderShell implements Provider {
return httpServer;
}
listen() {
async listen() {
this.#httpServer.listen(this.#httpPort, this.options.host ?? "0.0.0.0");
await this.tasks.init();
}
}
+4
View File
@@ -1808,6 +1808,10 @@ importers:
version: 5.3.2
packages/core-apps:
dependencies:
execa:
specifier: ^8.0.1
version: 8.0.1
devDependencies:
'@trigger.dev/core':
specifier: workspace:*