Skip to content

Commit 1a7df3f

Browse files
guy-borderlessGuy Kedemmatt-aitken
authored
feat: enable deno (#531)
* adding deno reference project and JSRuntime file * accessing runtime specific functions through JsRuntime * adding support for both deno.json and deno.jsonc * no automatic setup for deno projects currently --------- Co-authored-by: Guy Kedem <[email protected]> Co-authored-by: Matt Aitken <[email protected]>
1 parent 70410d3 commit 1a7df3f

File tree

11 files changed

+911
-43
lines changed

11 files changed

+911
-43
lines changed

.vscode/extensions.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"recommendations": [
3+
"astro-build.astro-vscode",
4+
"denoland.vscode-deno"
5+
],
6+
"unwantedRecommendations": [
7+
8+
]
9+
}

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"deno.enablePaths": ["references/deno-reference"]
3+
}

packages/cli/src/commands/dev.ts

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import childProcess from "child_process";
44
import chokidar from "chokidar";
55
import fs from "fs/promises";
66
import ngrok from "ngrok";
7-
import { run as ncuRun } from "npm-check-updates";
7+
import run, { run as ncuRun } from "npm-check-updates";
88
import ora, { Ora } from "ora";
99
import pRetry, { AbortError } from "p-retry";
1010
import pathModule from "path";
@@ -22,6 +22,8 @@ import { resolvePath } from "../utils/parseNameAndPath";
2222
import { RequireKeys } from "../utils/requiredKeys";
2323
import { Throttle } from "../utils/throttle";
2424
import { TriggerApi } from "../utils/triggerApi";
25+
import { standardWatchIgnoreRegex, standardWatchFilePaths } from "../frameworks/watchConfig";
26+
import { JsRuntime, getJsRuntime } from "../utils/jsRuntime";
2527
import { wait } from "../utils/wait";
2628

2729
const asyncExecFile = util.promisify(childProcess.execFile);
@@ -45,6 +47,7 @@ const formattedDate = new Intl.DateTimeFormat("en", {
4547
second: "numeric",
4648
});
4749

50+
let runtime: JsRuntime;
4851
export async function devCommand(path: string, anyOptions: any) {
4952
telemetryClient.dev.started(path, anyOptions);
5053

@@ -57,12 +60,12 @@ export async function devCommand(path: string, anyOptions: any) {
5760
const options = result.data;
5861

5962
const resolvedPath = resolvePath(path);
60-
63+
runtime = await getJsRuntime(resolvedPath, logger);
6164
//check for outdated packages, don't await this
62-
checkForOutdatedPackages(resolvedPath);
65+
runtime.checkForOutdatedPackages();
6366

6467
// Read from package.json to get the endpointId
65-
const endpointId = await getEndpointIdFromPackageJson(resolvedPath, options);
68+
const endpointId = await getEndpointId(runtime, options);
6669
if (!endpointId) {
6770
logger.error(
6871
"You must run the `init` command first to setup the project – you are missing \n'trigger.dev': { 'endpointId': 'your-client-id' } from your package.json file, or pass in the --client-id option to this command"
@@ -73,8 +76,8 @@ export async function devCommand(path: string, anyOptions: any) {
7376
logger.success(`✔️ [trigger.dev] Detected TriggerClient id: ${endpointId}`);
7477

7578
//resolve the options using the detected framework (use default if there isn't a matching framework)
76-
const packageManager = await getUserPackageManager(resolvedPath);
77-
const framework = await getFramework(resolvedPath, packageManager);
79+
const packageManager = await runtime.getUserPackageManager();
80+
const framework = await runtime.getFramework();
7881
const resolvedOptions = await resolveOptions(framework, resolvedPath, options);
7982

8083
// Read from .env.local or .env to get the TRIGGER_API_KEY and TRIGGER_API_URL
@@ -170,6 +173,7 @@ async function refresh(options: RefreshOptions) {
170173
return;
171174
}
172175

176+
const refreshedEndpointId = await getEndpointId(runtime, resolvedOptions);
173177
const { apiKey, apiUrl } = apiDetails;
174178
const apiClient = new TriggerApi(apiKey, apiUrl);
175179

@@ -396,43 +400,10 @@ async function verifyEndpoint(
396400
return;
397401
}
398402

399-
export async function checkForOutdatedPackages(path: string) {
400-
const updates = (await ncuRun({
401-
packageFile: `${path}/package.json`,
402-
filter: "/trigger.dev/.+$/",
403-
upgrade: false,
404-
})) as {
405-
[key: string]: string;
406-
};
407-
408-
if (typeof updates === "undefined" || Object.keys(updates).length === 0) {
409-
return;
410-
}
411-
412-
const packageFile = await fs.readFile(`${path}/package.json`);
413-
const data = JSON.parse(Buffer.from(packageFile).toString("utf8"));
414-
const dependencies = data.dependencies;
415-
console.log(chalk.bgYellow("Updates available for trigger.dev packages"));
416-
console.log(chalk.bgBlue("Run npx @trigger.dev/cli@latest update"));
417-
418-
for (let dep in updates) {
419-
console.log(`${dep} ${dependencies[dep]}${updates[dep]}`);
420-
}
421-
}
422-
423-
export async function getEndpointIdFromPackageJson(path: string, options: DevCommandOptions) {
403+
export function getEndpointId(runtime: JsRuntime, options: DevCommandOptions) {
424404
if (options.clientId) {
425405
return options.clientId;
426-
}
427-
428-
const pkgJsonPath = pathModule.join(path, "package.json");
429-
const pkgBuffer = await fs.readFile(pkgJsonPath);
430-
const pkgJson = JSON.parse(pkgBuffer.toString());
431-
432-
const value = pkgJson["trigger.dev"]?.endpointId;
433-
if (!value || typeof value !== "string") return;
434-
435-
return value as string;
406+
} else return runtime.getEndpointId();
436407
}
437408

438409
async function resolveEndpointUrl(apiUrl: string, port: number, hostname: string) {

packages/cli/src/commands/init.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { resolvePath } from "../utils/parseNameAndPath";
2222
import { readPackageJson } from "../utils/readPackageJson";
2323
import { renderTitle } from "../utils/renderTitle";
2424
import { TriggerApi, WhoamiResponse } from "../utils/triggerApi";
25+
import { getJsRuntime } from "../utils/jsRuntime";
2526

2627
export type InitCommandOptions = {
2728
projectPath: string;
@@ -38,6 +39,17 @@ export const initCommand = async (options: InitCommandOptions) => {
3839

3940
const resolvedPath = resolvePath(options.projectPath);
4041

42+
// assuming nodejs by default
43+
let runtimeId: string = "nodejs";
44+
try {
45+
runtimeId = (await getJsRuntime(resolvedPath, logger)).id;
46+
} catch {}
47+
if (runtimeId !== "nodejs") {
48+
logger.error(`We currently only support automatic setup for NodeJS projects.`);
49+
telemetryClient.init.failed("not_supported_runtime", options);
50+
return;
51+
}
52+
4153
await renderTitle(resolvedPath);
4254

4355
if (options.triggerUrl === CLOUD_TRIGGER_URL) {

packages/cli/src/commands/whoami.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { z } from "zod";
22
import { logger } from "../utils/logger";
33
import { resolvePath } from "../utils/parseNameAndPath";
44
import { TriggerApi } from "../utils/triggerApi";
5-
import { DevCommandOptions, getEndpointIdFromPackageJson } from "./dev";
5+
import { DevCommandOptions, getEndpointId } from "./dev";
66
import ora from "ora";
77
import { getTriggerApiDetails } from "../utils/getTriggerApiDetails";
88

@@ -26,7 +26,7 @@ export async function whoamiCommand(path: string, anyOptions: any) {
2626
const resolvedPath = resolvePath(path);
2727

2828
// Read from package.json to get the endpointId
29-
const endpointId = await getEndpointIdFromPackageJson(resolvedPath, options as DevCommandOptions);
29+
const endpointId = await getEndpointId(resolvedPath, options as DevCommandOptions);
3030
if (!endpointId) {
3131
logger.error(
3232
"You must run the `init` command first to setup the project – you are missing \n'trigger.dev': { 'endpointId': 'your-client-id' } from your package.json file, or pass in the --client-id option to this command"
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { Framework, getFramework } from "../frameworks";
2+
import { PackageManager, getUserPackageManager } from "./getUserPkgManager";
3+
import { Logger } from "./logger";
4+
import { run as ncuRun } from "npm-check-updates";
5+
import chalk from "chalk";
6+
import fs from "fs/promises";
7+
import pathModule from "path";
8+
9+
export abstract class JsRuntime {
10+
logger: Logger;
11+
projectRootPath: string;
12+
constructor(projectRootPath: string, logger: Logger) {
13+
this.logger = logger;
14+
this.projectRootPath = projectRootPath;
15+
}
16+
abstract get id(): string;
17+
abstract checkForOutdatedPackages(): Promise<void>;
18+
abstract getUserPackageManager(): Promise<PackageManager | undefined>;
19+
abstract getFramework(): Promise<Framework | undefined>;
20+
abstract getEndpointId(): Promise<string | undefined>;
21+
}
22+
23+
export async function getJsRuntime(projectRootPath: string, logger: Logger): Promise<JsRuntime> {
24+
if (await NodeJsRuntime.isNodeJsRuntime(projectRootPath)) {
25+
return new NodeJsRuntime(projectRootPath, logger);
26+
} else if (await DenoRuntime.isDenoJsRuntime(projectRootPath)) {
27+
return new DenoRuntime(projectRootPath, logger);
28+
}
29+
throw new Error("Unsupported runtime");
30+
}
31+
32+
class NodeJsRuntime extends JsRuntime {
33+
static async isNodeJsRuntime(projectRootPath: string): Promise<boolean> {
34+
try {
35+
await fs.stat(pathModule.join(projectRootPath, "package.json"));
36+
return true;
37+
} catch {
38+
return false;
39+
}
40+
}
41+
42+
get id() {
43+
return "nodejs";
44+
}
45+
get packageJsonPath(): string {
46+
return pathModule.join(this.projectRootPath, "package.json");
47+
}
48+
49+
async checkForOutdatedPackages(): Promise<void> {
50+
const updates = (await ncuRun({
51+
packageFile: `${this.packageJsonPath}`,
52+
filter: "/trigger.dev/.+$/",
53+
upgrade: false,
54+
})) as {
55+
[key: string]: string;
56+
};
57+
58+
if (typeof updates === "undefined" || Object.keys(updates).length === 0) {
59+
return;
60+
}
61+
62+
const packageFile = await fs.readFile(this.packageJsonPath);
63+
const data = JSON.parse(Buffer.from(packageFile).toString("utf8"));
64+
const dependencies = data.dependencies;
65+
console.log(chalk.bgYellow("Updates available for trigger.dev packages"));
66+
console.log(chalk.bgBlue("Run npx @trigger.dev/cli@latest update"));
67+
68+
for (let dep in updates) {
69+
console.log(`${dep} ${dependencies[dep]}${updates[dep]}`);
70+
}
71+
}
72+
73+
async getUserPackageManager() {
74+
return getUserPackageManager(this.projectRootPath);
75+
}
76+
77+
async getFramework() {
78+
const userPackageManager = await this.getUserPackageManager();
79+
return getFramework(this.projectRootPath, userPackageManager);
80+
}
81+
async getEndpointId() {
82+
const pkgJsonPath = pathModule.join(this.projectRootPath, "package.json");
83+
const pkgBuffer = await fs.readFile(pkgJsonPath);
84+
const pkgJson = JSON.parse(pkgBuffer.toString());
85+
const value = pkgJson["trigger.dev"]?.endpointId;
86+
if (!value || typeof value !== "string") return undefined;
87+
}
88+
}
89+
90+
class DenoRuntime extends JsRuntime {
91+
getDenoJsonPath(): Promise<string> {
92+
try {
93+
return fs
94+
.stat(pathModule.join(this.projectRootPath, "deno.json"))
95+
.then(() => pathModule.join(this.projectRootPath, "deno.json"));
96+
} catch {
97+
return fs
98+
.stat(pathModule.join(this.projectRootPath, "deno.jsonc"))
99+
.then(() => pathModule.join(this.projectRootPath, "deno.jsonc"));
100+
}
101+
}
102+
103+
get id() {
104+
return "deno";
105+
}
106+
107+
static async isDenoJsRuntime(projectRootPath: string): Promise<boolean> {
108+
try {
109+
try {
110+
await fs.stat(pathModule.join(projectRootPath, "deno.json"));
111+
} catch (e) {
112+
await fs.stat(pathModule.join(projectRootPath, "deno.jsonc"));
113+
}
114+
return true;
115+
} catch {
116+
return false;
117+
}
118+
}
119+
120+
async checkForOutdatedPackages() {
121+
// not implemented currently
122+
}
123+
async getUserPackageManager() {
124+
return undefined;
125+
}
126+
async getFramework() {
127+
// not implemented currently
128+
return undefined;
129+
}
130+
async getEndpointId() {
131+
const pkgJsonPath = await this.getDenoJsonPath();
132+
const pkgBuffer = await fs.readFile(pkgJsonPath);
133+
const pkgJson = JSON.parse(pkgBuffer.toString());
134+
return pkgJson["trigger.dev"]?.endpointId;
135+
}
136+
}

packages/cli/src/utils/logger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import chalk from "chalk";
22

3+
export type Logger = typeof logger;
34
export const logger = {
45
error(...args: unknown[]) {
56
console.log(chalk.red(...args));
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"deno.enable": true
3+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"trigger.dev": {
3+
"endpointId": "borderless"
4+
},
5+
"tasks": {
6+
"dev": "deno run --watch main.ts"
7+
}
8+
}

0 commit comments

Comments
 (0)