v3: misc CLI improvements (#1173)

* prevent downgrades during update check

* detect bun and use npm instead

* detect missing tsconfig during init and print helpful error

* add changeset

* add links to dev worker started message

* allow users to add custom pkg manager args during init

* update changeset

* fix links in unsupported terminals

* deprecate terminalLink

* update changeset
This commit is contained in:
nicktrn
2024-06-28 15:51:17 +01:00
committed by GitHub
parent 568da01785
commit d0d3a64bd6
15 changed files with 357 additions and 78 deletions
+10
View File
@@ -0,0 +1,10 @@
---
"trigger.dev": patch
---
- Prevent downgrades during update check and advise to upgrade CLI
- Detect bun and use npm instead
- During init, fail early and advise if not a TypeScript project
- During init, allow specifying custom package manager args
- Add links to dev worker started message
- Fix links in unsupported terminals
+7 -1
View File
@@ -84,7 +84,9 @@ export function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `pk_${envSlug(envType)}_${apiKeyId(20)}`;
}
export function envSlug(environmentType: RuntimeEnvironment["type"]) {
export type EnvSlug = "dev" | "stg" | "prod" | "prev";
export function envSlug(environmentType: RuntimeEnvironment["type"]): EnvSlug {
switch (environmentType) {
case "DEVELOPMENT": {
return "dev";
@@ -100,3 +102,7 @@ export function envSlug(environmentType: RuntimeEnvironment["type"]) {
}
}
}
export function isEnvSlug(maybeSlug: string): maybeSlug is EnvSlug {
return ["dev", "stg", "prod", "prev"].includes(maybeSlug);
}
@@ -0,0 +1,74 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { EnvSlug, isEnvSlug } from "~/models/api-key.server";
import { requireUserId } from "~/services/session.server";
const ParamsSchema = z.object({
projectRef: z.string(),
});
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
const { projectRef } = ParamsSchema.parse(params);
const project = await prisma.project.findFirst({
where: {
externalRef: projectRef,
organization: {
members: {
some: {
userId,
},
},
},
},
include: {
organization: true,
},
});
if (!project) {
return new Response("Project not found", { status: 404 });
}
const url = new URL(request.url);
const envSlug = url.searchParams.get("envSlug");
// Get the environment from the slug
if (envSlug && isEnvSlug(envSlug)) {
const env = await getEnvFromSlug(project.id, userId, envSlug);
if (env) {
url.searchParams.set("environments", env.id);
}
url.searchParams.delete("envSlug");
}
return redirect(
`/orgs/${project.organization.slug}/projects/v3/${project.slug}/runs${url.search}`
);
}
async function getEnvFromSlug(projectId: string, userId: string, envSlug: EnvSlug) {
if (envSlug === "dev") {
return await prisma.runtimeEnvironment.findFirst({
where: {
projectId,
slug: envSlug,
orgMember: {
userId,
},
},
});
}
return await prisma.runtimeEnvironment.findFirst({
where: {
projectId,
slug: envSlug,
},
});
}
@@ -0,0 +1,40 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { requireUserId } from "~/services/session.server";
const ParamsSchema = z.object({
projectRef: z.string(),
});
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
const validatedParams = ParamsSchema.parse(params);
const project = await prisma.project.findFirst({
where: {
externalRef: validatedParams.projectRef,
organization: {
members: {
some: {
userId,
},
},
},
},
include: {
organization: true,
},
});
if (!project) {
return new Response("Not found", { status: 404 });
}
const url = new URL(request.url);
return redirect(
`/orgs/${project.organization.slug}/projects/v3/${project.slug}/test${url.search}`
);
}
+2 -2
View File
@@ -3,7 +3,6 @@
import { log } from "@clack/prompts";
import { Metafile } from "esbuild";
import { join } from "node:path";
import terminalLink from "terminal-link";
import { SkipLoggingError } from "../src/cli/common.js";
import {
@@ -16,6 +15,7 @@ import { writeJSONFile } from "../src/utilities/fileSystem.js";
import { PackageManager } from "../src/utilities/getUserPackageManager.js";
import { JavascriptProject } from "../src/utilities/javascriptProject.js";
import { logger } from "../src/utilities/logger.js";
import { cliLink } from "../src/utilities/cliOutput.js";
type HandleDependenciesOptions = {
entryPointMetaOutput: Metafile["outputs"]["out/stdin.js"];
@@ -81,7 +81,7 @@ export async function handleDependencies(options: HandleDependenciesOptions) {
log.warn(
`No additionalFiles matches for:\n\n${copyResult.noMatches
.map((glob) => `- "${glob}"`)
.join("\n")}\n\nIf this is unexpected you should check your ${terminalLink(
.join("\n")}\n\nIf this is unexpected you should check your ${cliLink(
"glob patterns",
"https://github.com/isaacs/node-glob?tab=readme-ov-file#glob-primer"
)} are valid.`
+7 -8
View File
@@ -16,7 +16,6 @@ import { readFileSync } from "node:fs";
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join, posix, relative, resolve } from "node:path";
import { setTimeout } from "node:timers/promises";
import terminalLink from "terminal-link";
import invariant from "tiny-invariant";
import { z } from "zod";
import * as packageJson from "../../package.json";
@@ -50,7 +49,7 @@ import {
mockServerOnlyPlugin,
workerSetupImportConfigPlugin,
} from "../utilities/build";
import { chalkError, chalkPurple, chalkWarning } from "../utilities/cliOutput";
import { chalkError, chalkPurple, chalkWarning, cliLink } from "../utilities/cliOutput";
import {
logESMRequireError,
logTaskMetadataParseError,
@@ -437,7 +436,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
throw new SkipLoggingError(`Deployment failed to complete: ${finishedDeployment}`);
}
const deploymentLink = terminalLink(
const deploymentLink = cliLink(
"View deployment",
`${authorization.dashboardUrl}/projects/v3/${resolvedConfig.config.project}/deployments/${finishedDeployment.shortCode}`
);
@@ -576,7 +575,7 @@ function checkLogsForWarnings(logs: string): WarningsCheckReturn {
const warnings: LogParserOptions = [
{
regex: /prisma:warn We could not find your Prisma schema/,
message: `Prisma generate failed to find the default schema. Did you include it in config.additionalFiles? ${terminalLink(
message: `Prisma generate failed to find the default schema. Did you include it in config.additionalFiles? ${cliLink(
"Config docs",
docs.config.prisma
)}\nCustom schema paths require a postinstall script like this: \`prisma generate --schema=./custom/path/to/schema.prisma\``,
@@ -626,17 +625,17 @@ function checkLogsForErrors(logs: string) {
const errors: LogParserOptions = [
{
regex: /Error: Provided --schema at (?<schema>.*) doesn't exist/,
message: `Prisma generate failed to find the specified schema at "$schema".\nDid you include it in config.additionalFiles? ${terminalLink(
message: `Prisma generate failed to find the specified schema at "$schema".\nDid you include it in config.additionalFiles? ${cliLink(
"Config docs",
docs.config.prisma
)}`,
},
{
regex: /sh: 1: (?<packageOrBinary>.*): not found/,
message: `$packageOrBinary not found\n\nIf it's a package: Include it in ${terminalLink(
message: `$packageOrBinary not found\n\nIf it's a package: Include it in ${cliLink(
"config.additionalPackages",
docs.config.prisma
)}\nIf it's a binary: Please ${terminalLink(
)}\nIf it's a binary: Please ${cliLink(
"get in touch",
getInTouch
)} and we'll see what we can do!`,
@@ -1341,7 +1340,7 @@ async function compileProject(
log.warn(
`No additionalFiles matches for:\n\n${copyResult.noMatches
.map((glob) => `- "${glob}"`)
.join("\n")}\n\nIf this is unexpected you should check your ${terminalLink(
.join("\n")}\n\nIf this is unexpected you should check your ${cliLink(
"glob patterns",
"https://github.com/isaacs/node-glob?tab=readme-ov-file#glob-primer"
)} are valid.`
+24 -6
View File
@@ -29,7 +29,15 @@ import {
mockServerOnlyPlugin,
workerSetupImportConfigPlugin,
} from "../utilities/build";
import { chalkError, chalkGrey, chalkPurple, chalkTask, chalkWorker } from "../utilities/cliOutput";
import {
chalkError,
chalkGrey,
chalkLink,
chalkPurple,
chalkTask,
chalkWorker,
cliLink,
} from "../utilities/cliOutput";
import { readConfig } from "../utilities/configFiles";
import { readJSONFile } from "../utilities/fileSystem";
import { printDevBanner, printStandloneInitialBanner } from "../utilities/initialBanner.js";
@@ -624,13 +632,23 @@ function useDev({
}
backgroundWorker.metadata = backgroundWorkerRecord.data;
backgroundWorker;
const testUrl = `${dashboardUrl}/projects/v3/${config.project}/test?environment=dev`;
const runsUrl = `${dashboardUrl}/projects/v3/${config.project}/runs?envSlug=dev`;
const pipe = chalkGrey("|");
const bullet = chalkGrey("○");
const arrow = chalkGrey("->");
const testLink = chalkLink(cliLink("Test tasks", testUrl));
const runsLink = chalkLink(cliLink("View runs", runsUrl));
const workerStarted = chalkGrey("Background worker started");
const workerVersion = chalkWorker(backgroundWorkerRecord.data.version);
logger.log(
`${chalkGrey(
`○ Background worker started -> ${chalkWorker(
backgroundWorkerRecord.data.version
)}`
)}`
`${bullet} ${workerStarted} ${arrow} ${workerVersion} ${pipe} ${testLink} ${pipe} ${runsLink}`
);
firstBuild = false;
+93 -38
View File
@@ -4,11 +4,10 @@ import { GetProjectResponseBody, flattenAttributes } from "@trigger.dev/core/v3"
import { recordSpanException } from "@trigger.dev/core/v3/workers";
import chalk from "chalk";
import { Command } from "commander";
import { execa } from "execa";
import { ExecaError, Options as ExecaOptions, ResultPromise as ExecaResult, execa } from "execa";
import { applyEdits, modify, findNodeAtLocation, parseTree, getNodeValue } from "jsonc-parser";
import { writeFile } from "node:fs/promises";
import { join, relative, resolve } from "node:path";
import terminalLink from "terminal-link";
import { z } from "zod";
import { CliApiClient } from "../apiClient";
import {
@@ -24,7 +23,7 @@ import {
import { readConfig } from "../utilities/configFiles.js";
import { createFileFromTemplate } from "../utilities/createFileFromTemplate";
import { createFile, pathExists, readFile } from "../utilities/fileSystem";
import { getUserPackageManager } from "../utilities/getUserPackageManager";
import { PackageManager, getUserPackageManager } from "../utilities/getUserPackageManager";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
import { logger } from "../utilities/logger";
import { cliRootPath } from "../utilities/resolveInternalFilePath";
@@ -32,12 +31,14 @@ import { login } from "./login";
import { spinner } from "../utilities/windows";
import { CLOUD_API_URL } from "../consts";
import * as packageJson from "../../package.json";
import { cliLink, prettyError } from "../utilities/cliOutput";
const InitCommandOptions = CommonCommandOptions.extend({
projectRef: z.string().optional(),
overrideConfig: z.boolean().default(false),
tag: z.string().default("beta"),
skipPackageInstall: z.boolean().default(false),
pkgArgs: z.string().optional(),
});
type InitCommandOptions = z.infer<typeof InitCommandOptions>;
@@ -59,6 +60,10 @@ export function configureInitCommand(program: Command) {
)
.option("--skip-package-install", "Skip installing the @trigger.dev/sdk package")
.option("--override-config", "Override the existing config file if it exists")
.option(
"--pkg-args <args>",
"Additional arguments to pass to the package manager, accepts CSV for multiple args"
)
).action(async (path, options) => {
await handleTelemetry(async () => {
await printStandloneInitialBanner(true);
@@ -78,6 +83,9 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
intro("Initializing project");
// Detect tsconfig.json and exit if not found
await detectTsConfig(dir, options);
const authorization = await login({
embedded: true,
defaultApiUrl: options.apiUrl,
@@ -153,7 +161,7 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
// Ignore .trigger dir
await gitIgnoreDotTriggerDir(dir, options);
const projectDashboard = terminalLink(
const projectDashboard = cliLink(
"project dashboard",
`${authorization.dashboardUrl}/projects/v3/${selectedProject.externalRef}`
);
@@ -169,13 +177,10 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
);
log.info(` 2. Visit your ${projectDashboard} to view your newly created tasks.`);
log.info(
` 3. Head over to our ${terminalLink(
"v3 docs",
"https://trigger.dev/docs/v3"
)} to learn more.`
` 3. Head over to our ${cliLink("v3 docs", "https://trigger.dev/docs/v3")} to learn more.`
);
log.info(
` 4. Need help? Join our ${terminalLink(
` 4. Need help? Join our ${cliLink(
"Discord community",
"https://trigger.dev/discord"
)} or email us at ${chalk.cyan("help@trigger.dev")}`
@@ -321,8 +326,46 @@ async function gitIgnoreDotTriggerDir(dir: string, options: InitCommandOptions)
});
}
async function detectTsConfig(dir: string, options: InitCommandOptions) {
return await tracer.startActiveSpan("detectTsConfig", async (span) => {
try {
const projectDir = resolve(process.cwd(), dir);
const tsconfigPath = join(projectDir, "tsconfig.json");
span.setAttributes({
"cli.projectDir": projectDir,
"cli.tsconfigPath": tsconfigPath,
});
const tsconfigExists = await pathExists(tsconfigPath);
if (!tsconfigExists) {
prettyError(
"No tsconfig.json found",
`The init command needs to be run in a TypeScript project. You can create one like this:`,
`npm install typescript --save-dev\nnpx tsc --init\n`
);
throw new Error("TypeScript required");
}
logger.debug("tsconfig.json exists", { tsconfigPath });
span.end();
} catch (e) {
if (!(e instanceof SkipCommandError)) {
recordSpanException(span, e);
}
span.end();
throw e;
}
});
}
async function addConfigFileToTsConfig(dir: string, options: InitCommandOptions) {
return await tracer.startActiveSpan("createTriggerDir", async (span) => {
return await tracer.startActiveSpan("addConfigFileToTsConfig", async (span) => {
try {
const projectDir = resolve(process.cwd(), dir);
const tsconfigPath = join(projectDir, "tsconfig.json");
@@ -391,9 +434,12 @@ async function installPackages(dir: string, options: InitCommandOptions) {
return await tracer.startActiveSpan("installPackages", async (span) => {
const installSpinner = spinner();
let pkgManager: PackageManager | undefined;
try {
const projectDir = resolve(process.cwd(), dir);
const pkgManager = await getUserPackageManager(projectDir);
pkgManager = await getUserPackageManager(projectDir);
span.setAttributes({
"cli.projectDir": projectDir,
@@ -401,54 +447,63 @@ async function installPackages(dir: string, options: InitCommandOptions) {
"cli.tag": options.tag,
});
const userArgs = options.pkgArgs?.split(",") ?? [];
const execaOptions = { cwd: projectDir } satisfies ExecaOptions;
let installProcess: ExecaResult<typeof execaOptions>;
let args: string[];
switch (pkgManager) {
case "npm": {
installSpinner.start(`Running 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",
});
break;
}
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",
});
args = ["install", "--save-exact", ...userArgs, `@trigger.dev/sdk@${options.tag}`];
break;
}
case "pnpm":
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",
});
args = ["add", ...userArgs, `@trigger.dev/sdk@${options.tag}`];
break;
}
}
installSpinner.start(`Running ${pkgManager} ${args.join(" ")}`);
installProcess = execa(pkgManager, args, execaOptions);
const handleProcessOutput = (data: Buffer) => {
logger.debug(data.toString());
};
installProcess.stderr?.on("data", handleProcessOutput);
installProcess.stdout?.on("data", handleProcessOutput);
await installProcess;
installSpinner.stop(`@trigger.dev/sdk@${options.tag} installed`);
span.end();
} catch (e) {
installSpinner.stop(
`Failed to install @trigger.dev/sdk@${options.tag}. Rerun command with --log-level debug for more details.`
);
if (options.logLevel === "debug") {
installSpinner.stop(`Failed to install @trigger.dev/sdk@${options.tag}.`);
} else {
installSpinner.stop(
`Failed to install @trigger.dev/sdk@${options.tag}. Rerun command with --log-level debug for more details.`
);
}
if (!(e instanceof SkipCommandError)) {
recordSpanException(span, e);
}
if (e instanceof ExecaError) {
if (pkgManager) {
e.message += ` \n\nNote: You can pass additional args to ${pkgManager} by using --pkg-args. For example: trigger.dev init --pkg-args="--workspace-root"`;
}
}
span.end();
throw e;
@@ -546,7 +601,7 @@ async function selectProject(apiClient: CliApiClient, dashboardUrl: string, proj
}
if (projectsResponse.data.length === 0) {
const newProjectLink = terminalLink(
const newProjectLink = cliLink(
"Create new project",
`${dashboardUrl}/projects/new?version=v3`
);
+71 -17
View File
@@ -11,7 +11,7 @@ import { join, resolve } from "path";
import { JavascriptProject } from "../utilities/javascriptProject.js";
import { PackageManager } from "../utilities/getUserPackageManager.js";
import { getVersion } from "../utilities/getVersion.js";
import { chalkError, prettyWarning } from "../utilities/cliOutput.js";
import { chalkError, prettyError, prettyWarning } from "../utilities/cliOutput.js";
export const UpdateCommandOptions = CommonCommandOptions.pick({
logLevel: true,
@@ -81,7 +81,13 @@ export async function updateTriggerPackages(
const triggerDependencies = getTriggerDependencies(packageJson);
function getVersionMismatches(deps: Dependency[], targetVersion: string): Dependency[] {
function getVersionMismatches(
deps: Dependency[],
targetVersion: string
): {
mismatches: Dependency[];
isDowngrade: boolean;
} {
const mismatches: Dependency[] = [];
for (const dep of deps) {
@@ -92,12 +98,35 @@ export async function updateTriggerPackages(
mismatches.push(dep);
}
return mismatches;
const extractRelease = (version: string) => {
const release = Number(version.split("3.0.0-beta.")[1]);
return release || undefined;
};
let isDowngrade = false;
const targetRelease = extractRelease(targetVersion);
if (targetRelease) {
isDowngrade = mismatches.some((dep) => {
const depRelease = extractRelease(dep.version);
if (!depRelease) {
return false;
}
return depRelease > targetRelease;
});
}
return {
mismatches,
isDowngrade,
};
}
const versionMismatches = getVersionMismatches(triggerDependencies, cliVersion);
const { mismatches, isDowngrade } = getVersionMismatches(triggerDependencies, cliVersion);
if (versionMismatches.length === 0) {
if (mismatches.length === 0) {
if (!embedded) {
outro(`Nothing to do${newCliVersion ? " ..but you should really update your CLI!" : ""}`);
return hasOutput;
@@ -105,10 +134,14 @@ export async function updateTriggerPackages(
return hasOutput;
}
prettyWarning(
"Mismatch between your CLI version and installed packages",
"We recommend pinned versions for guaranteed compatibility"
);
if (isDowngrade) {
prettyError("Some of the installed @trigger.dev packages are newer than your CLI version");
} else {
prettyWarning(
"Mismatch between your CLI version and installed packages",
"We recommend pinned versions for guaranteed compatibility"
);
}
if (!process.stdout.isTTY) {
// Running in CI with version mismatch detected
@@ -124,7 +157,22 @@ export async function updateTriggerPackages(
CLI version: ${cliVersion}
Current package versions that don't match the CLI:
${versionMismatches.map((dep) => `- ${dep.name}@${dep.version}`).join("\n")}\n`
${mismatches.map((dep) => `- ${dep.name}@${dep.version}`).join("\n")}\n`
);
process.exit(1);
}
// WARNING: We can only start accepting user input once we know this is a TTY, otherwise, the process will exit with an error in CI
if (isDowngrade) {
printUpdateTable("Versions", mismatches, cliVersion, "installed", "CLI");
outro("CLI update required!");
logger.log(
`${chalkError(
"X Error:"
)} Please update your CLI. Alternatively, use \`--skip-update-check\` at your own risk.\n`
);
process.exit(1);
}
@@ -132,7 +180,7 @@ export async function updateTriggerPackages(
log.message(""); // spacing
// Always require user confirmation
const userWantsToUpdate = await updateConfirmation(versionMismatches, cliVersion);
const userWantsToUpdate = await updateConfirmation(mismatches, cliVersion);
if (isCancel(userWantsToUpdate)) {
throw new OutroCommandError();
@@ -175,7 +223,7 @@ export async function updateTriggerPackages(
process.prependOnceListener("exit", exitHandler);
// Update package.json
mutatePackageJsonWithUpdatedPackages(packageJson, versionMismatches, cliVersion);
mutatePackageJsonWithUpdatedPackages(packageJson, mismatches, cliVersion);
await writeJSONFile(packageJsonPath, packageJson, true);
async function revertPackageJsonChanges() {
@@ -274,20 +322,26 @@ function mutatePackageJsonWithUpdatedPackages(
}
}
function printUpdateTable(depsToUpdate: Dependency[], targetVersion: string): void {
log.message("Suggested updates");
function printUpdateTable(
heading: string,
depsToUpdate: Dependency[],
targetVersion: string,
oldColumn = "old",
newColumn = "new"
): void {
log.message(heading);
const tableData = depsToUpdate.map((dep) => ({
package: dep.name,
old: dep.version,
new: targetVersion,
[oldColumn]: dep.version,
[newColumn]: targetVersion,
}));
logger.table(tableData);
}
async function updateConfirmation(depsToUpdate: Dependency[], targetVersion: string) {
printUpdateTable(depsToUpdate, targetVersion);
printUpdateTable("Suggested updates", depsToUpdate, targetVersion);
let confirmMessage = "Would you like to apply those updates?";
@@ -1,5 +1,6 @@
import { log } from "@clack/prompts";
import chalk from "chalk";
import terminalLink, { Options as TerminalLinkOptions } from "terminal-link";
export const green = "#4FFF54";
export const purple = "#735BF3";
@@ -110,3 +111,10 @@ export function prettyWarning(header: string, body?: string, footer?: string) {
}`
);
}
export function cliLink(text: string, url: string, options?: TerminalLinkOptions) {
return terminalLink(text, url, {
fallback: (text, url) => `${text} ${url}`,
...options,
});
}
@@ -1,11 +1,10 @@
import chalk from "chalk";
import { relative } from "node:path";
import { chalkError, chalkPurple, chalkGrey, chalkGreen, chalkWarning } from "./cliOutput";
import { chalkError, chalkPurple, chalkGrey, chalkGreen, chalkWarning, cliLink } from "./cliOutput";
import { logger } from "./logger";
import { ReadConfigResult } from "./configFiles";
import { z } from "zod";
import { groupTaskMetadataIssuesByTask } from "@trigger.dev/core/v3";
import terminalLink from "terminal-link";
import { docs } from "./links";
export type ESMRequireError = {
@@ -114,7 +113,7 @@ export function logESMRequireError(parsedError: ESMRequireError, resolvedConfig:
}
logger.log(
`${chalkGrey("○")} For more info see the ${terminalLink("relevant docs", docs.config.esm)}.\n`
`${chalkGrey("○")} For more info see the ${cliLink("relevant docs", docs.config.esm)}.\n`
);
}
@@ -8,6 +8,7 @@ export const LOCKFILES = {
npmShrinkwrap: "npm-shrinkwrap.json",
pnpm: "pnpm-lock.yaml",
yarn: "yarn.lock",
bun: "bun.lockb",
};
export async function getUserPackageManager(path: string): Promise<PackageManager> {
@@ -53,11 +54,16 @@ export async function detectPackageManagerFromArtifacts(path: string): Promise<P
switch (basename(foundPath)) {
case LOCKFILES.yarn:
logger.debug("Found yarn artifact", { foundPath });
return "yarn";
case LOCKFILES.pnpm:
logger.debug("Found pnpm artifact", { foundPath });
return "pnpm";
case LOCKFILES.npm:
case LOCKFILES.npmShrinkwrap:
logger.debug("Found npm artifact", { foundPath });
case LOCKFILES.bun:
logger.debug("Found bun artifact", { foundPath });
return "npm";
default:
throw new Error(`Unhandled package manager detection path: ${foundPath}`);
@@ -23,7 +23,6 @@ import dotenv from "dotenv";
import { Evt } from "evt";
import { ChildProcess, fork } from "node:child_process";
import { dirname, resolve } from "node:path";
import terminalLink from "terminal-link";
import {
chalkError,
chalkGrey,
@@ -33,6 +32,7 @@ import {
chalkTask,
chalkWarning,
chalkWorker,
cliLink,
prettyPrintDate,
} from "../../utilities/cliOutput.js";
import { safeDeleteFileSync } from "../../utilities/fileSystem.js";
@@ -606,7 +606,7 @@ export class BackgroundWorker {
const pipe = chalkGrey("|");
const bullet = chalkGrey("○");
const link = chalkLink(terminalLink("View logs", logsUrl));
const link = chalkLink(cliLink("View logs", logsUrl));
let timestampPrefix = chalkGrey(prettyPrintDate(payload.execution.attempt.startedAt));
const workerPrefix = chalkWorker(this.metadata.version);
const taskPrefix = chalkTask(execution.task.id);
+1 -1
View File
@@ -1,6 +1,6 @@
// See: https://www.totaltypescript.com/tsconfig-cheat-sheet
{
"include": ["./src/**/*.ts", "./src/**/*.tsx", "./e2e/**/*.ts"],
"include": ["./src/**/*.ts", "./src/**/*.tsx", "./e2e/**/*.ts", "./types.d.ts"],
"compilerOptions": {
"esModuleInterop": true,
"skipLibCheck": true,
+10
View File
@@ -0,0 +1,10 @@
declare module "terminal-link" {
export interface Options {
fallback?: ((text: string, url: string) => string) | boolean;
}
/**
* @deprecated The default fallback is broken in some terminals. Please use `cliLink` instead.
*/
export default function terminalLink(text: string, url: string, options?: Options): string;
}