Skip to content

v3: Alternate config file no config file options #940

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

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions packages/cli-v3/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { z } from "zod";
import * as packageJson from "../../package.json";
import { CliApiClient } from "../apiClient";
import { CommonCommandOptions } from "../cli/common.js";
import { getConfigPath, readConfig } from "../utilities/configFiles.js";
import { readConfig } from "../utilities/configFiles.js";
import { createTempDir, readJSONFile, writeJSONFile } from "../utilities/fileSystem";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
import { detectPackageNameFromImportPath } from "../utilities/installPackages";
Expand All @@ -35,6 +35,8 @@ const DeployCommandOptions = CommonCommandOptions.extend({
selfHosted: z.boolean().default(false),
registry: z.string().optional(),
pushImage: z.boolean().default(false),
config: z.string().optional(),
projectRef: z.string().optional(),
});

type DeployCommandOptions = z.infer<typeof DeployCommandOptions>;
Expand All @@ -50,6 +52,15 @@ export function configureDeployCommand(program: Command) {
"prod"
)
.option("-T, --skip-typecheck", "Whether to skip the pre-build typecheck")
.option(
"-c, --config <config file>",
"The name of the config file, found at [path]",
"trigger.config.mjs"
)
.option(
"-p, --project-ref <project ref>",
"The project ref. Required if there is no config file."
)
.addOption(
new CommandOption(
"--self-hosted",
Expand Down Expand Up @@ -133,8 +144,12 @@ export async function deployCommand(dir: string, anyOptions: unknown) {

await printStandloneInitialBanner(true);

const configPath = await getConfigPath(dir);
const config = await readConfig(configPath);
const { config } = await readConfig(dir, {
configFile: options.data.config,
projectRef: options.data.projectRef,
});

logger.debug("Resolved config", { config });

const apiClient = new CliApiClient(authorization.config.apiUrl, authorization.config.accessToken);

Expand Down
56 changes: 41 additions & 15 deletions packages/cli-v3/src/commands/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,22 @@ import { z } from "zod";
import * as packageJson from "../../package.json";
import { CliApiClient } from "../apiClient";
import { CommonCommandOptions } from "../cli/common.js";
import { BackgroundWorker, BackgroundWorkerCoordinator } from "../workers/dev/backgroundWorker.js";
import { getConfigPath, readConfig } from "../utilities/configFiles";
import { readConfig } from "../utilities/configFiles";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
import { detectPackageNameFromImportPath } from "../utilities/installPackages";
import { logger } from "../utilities/logger.js";
import { isLoggedIn } from "../utilities/session.js";
import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles";
import { detectPackageNameFromImportPath } from "../utilities/installPackages";
import { UncaughtExceptionError } from "../workers/common/errors";
import { BackgroundWorker, BackgroundWorkerCoordinator } from "../workers/dev/backgroundWorker.js";

let apiClient: CliApiClient | undefined;

const DevCommandOptions = CommonCommandOptions.extend({
debugger: z.boolean().default(false),
debugOtel: z.boolean().default(false),
config: z.string().optional(),
projectRef: z.string().optional(),
});

type DevCommandOptions = z.infer<typeof DevCommandOptions>;
Expand All @@ -53,6 +55,15 @@ export function configureDevCommand(program: Command) {
"The log level to use (debug, info, log, warn, error, none)",
"log"
)
.option(
"-c, --config <config file>",
"The name of the config file, found at [path]",
"trigger.config.mjs"
)
.option(
"-p, --project-ref <project ref>",
"The project ref. Required if there is no config file."
)
.option("--debugger", "Enable the debugger")
.option("--debug-otel", "Enable OpenTelemetry debugging")
.action(async (path, options) => {
Expand Down Expand Up @@ -111,18 +122,30 @@ async function startDev(

await printStandloneInitialBanner(true);

const configPath = await getConfigPath(dir);
let config = await readConfig(configPath);

watcher = watch(configPath, {
persistent: true,
}).on("change", async (_event) => {
config = await readConfig(configPath);
logger.log(`${basename(configPath)} changed...`);
logger.debug("New config", { config });
rerender(await getDevReactElement(config, authorization));
let config = await readConfig(dir, {
projectRef: options.projectRef,
configFile: options.config,
});

logger.debug("Initial config", { config });

if (config.status === "file") {
watcher = watch(config.path, {
persistent: true,
}).on("change", async (_event) => {
config = await readConfig(dir, { configFile: options.config });

if (config.status === "file") {
logger.log(`${basename(config.path)} changed...`);
logger.debug("New config", { config: config.config });
rerender(await getDevReactElement(config.config, authorization));
} else {
logger.debug("New config", { config: config.config });
rerender(await getDevReactElement(config.config, authorization));
}
});
}

async function getDevReactElement(
configParam: ResolvedConfig,
authorization: { apiUrl: string; accessToken: string }
Expand All @@ -132,7 +155,10 @@ async function startDev(

apiClient = new CliApiClient(apiUrl, accessToken);

const devEnv = await apiClient.getProjectEnv({ projectRef: config.project, env: "dev" });
const devEnv = await apiClient.getProjectEnv({
projectRef: config.config.project,
env: "dev",
});

if (!devEnv.success) {
throw new Error(devEnv.error);
Expand All @@ -153,7 +179,7 @@ async function startDev(
);
}

const devReactElement = render(await getDevReactElement(config, authorization));
const devReactElement = render(await getDevReactElement(config.config, authorization));

rerender = devReactElement.rerender;

Expand Down
82 changes: 59 additions & 23 deletions packages/cli-v3/src/utilities/configFiles.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Config, ResolvedConfig } from "@trigger.dev/core/v3";
import { findUp } from "find-up";
import { mkdirSync, writeFileSync } from "node:fs";
import path, { dirname } from "node:path";
import path from "node:path";
import { pathToFileURL } from "node:url";
import xdgAppPaths from "xdg-app-paths";
import { z } from "zod";
import { CLOUD_API_URL, CONFIG_FILES } from "../consts.js";
import { readJSONFileSync } from "./fileSystem.js";
import { logger } from "./logger.js";
import { findUp } from "find-up";
import { CLOUD_API_URL, CONFIG_FILES } from "../consts.js";
import { pathToFileURL } from "node:url";
import { findTriggerDirectories, resolveTriggerDirectories } from "./taskFiles.js";
import { Config, ResolvedConfig } from "@trigger.dev/core/v3";

function getGlobalConfigFolderPath() {
const configDir = xdgAppPaths("trigger").config();
Expand Down Expand Up @@ -51,28 +51,64 @@ export function readAuthConfigFile(): UserAuthConfig | undefined {
}
}

export async function getConfigPath(dir: string): Promise<string> {
const path = await findUp(CONFIG_FILES, { cwd: dir });
async function getConfigPath(dir: string, fileName?: string): Promise<string | undefined> {
return await findUp(fileName ? [fileName] : CONFIG_FILES, { cwd: dir });
}

if (!path) {
throw new Error("No config file found.");
}
export type ReadConfigOptions = {
projectRef?: string;
configFile?: string;
};

export type ReadConfigResult =
| {
status: "file";
config: ResolvedConfig;
path: string;
}
| {
status: "in-memory";
config: ResolvedConfig;
};

export async function readConfig(
dir: string,
options?: ReadConfigOptions
): Promise<ReadConfigResult> {
const absoluteDir = path.resolve(process.cwd(), dir);

logger.debug("Searching for the config file", {
dir,
options,
absoluteDir,
});

return path;
}
const configPath = await getConfigPath(dir, options?.configFile);

export async function readConfig(path: string): Promise<ResolvedConfig> {
try {
// import the config file
const userConfigModule = await import(`${pathToFileURL(path).href}?_ts=${Date.now()}`);
const rawConfig = await normalizeConfig(userConfigModule ? userConfigModule.default : {});
const config = Config.parse(rawConfig);
if (!configPath) {
if (options?.projectRef) {
const rawConfig = await normalizeConfig({ project: options.projectRef });
const config = Config.parse(rawConfig);

return resolveConfig(path, config);
} catch (error) {
console.error(`Failed to load config file at ${path}`);
throw error;
return {
status: "in-memory",
config: await resolveConfig(absoluteDir, config),
};
} else {
throw new Error(`Config file not found in ${absoluteDir} or any parent directory.`);
}
}

// import the config file
const userConfigModule = await import(`${pathToFileURL(configPath).href}?_ts=${Date.now()}`);
const rawConfig = await normalizeConfig(userConfigModule ? userConfigModule.default : {});
const config = Config.parse(rawConfig);

return {
status: "file",
config: await resolveConfig(absoluteDir, config),
path: configPath,
};
}

export async function resolveConfig(path: string, config: Config): Promise<ResolvedConfig> {
Expand All @@ -87,7 +123,7 @@ export async function resolveConfig(path: string, config: Config): Promise<Resol
}

if (!config.projectDir) {
config.projectDir = dirname(path);
config.projectDir = path;
}

return config as ResolvedConfig;
Expand Down
7 changes: 3 additions & 4 deletions packages/cli-v3/src/utilities/taskFiles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ResolvedConfig } from "@trigger.dev/core/v3";
import fs from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { join, relative, resolve } from "node:path";
import { TaskFile } from "../types";
import { ResolvedConfig } from "@trigger.dev/core/v3";

export function createTaskFileImports(taskFiles: TaskFile[]) {
return taskFiles
Expand Down Expand Up @@ -45,8 +45,7 @@ export function resolveTriggerDirectories(dirs: string[]): string[] {

const IGNORED_DIRS = ["node_modules", ".git", "dist", "build"];

export async function findTriggerDirectories(filePath: string): Promise<string[]> {
const dirPath = dirname(filePath);
export async function findTriggerDirectories(dirPath: string): Promise<string[]> {
return getTriggerDirectories(dirPath);
}

Expand Down