Monorepo packages automatic handling (#1208)
* Add fixture * Update fixture name * Update fixture to work with npm & pnpm * Update setup/teardown and README * Add support for monorepo to e2e suite utilities * Use direct dependencies info in esbuild plugin * Renaming * Add support for yarn * Add fixture for npm * Remove console.log call * Edge cases * Remove unused options * Fix error unknown yarn in CI * Fix logger debug call's error field * Add changeset * Fix missing span end
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Automatically bundle internal packages that use file, link or workspace protocl
|
||||
@@ -42,6 +42,9 @@ jobs:
|
||||
- name: 🔧 Build worker template files
|
||||
run: pnpm --filter trigger.dev run build:workers
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Run E2E Tests
|
||||
run: |
|
||||
PM=${{ matrix.package-manager }} pnpm --filter trigger.dev run test:e2e
|
||||
|
||||
@@ -117,9 +117,9 @@ This will test your fixture project, and generate outputs in the `packages/cli-v
|
||||
|
||||
```sh
|
||||
cd packages/cli-v3/e2e/fixtures/<fixture-name>
|
||||
rm -rf node_modules
|
||||
rm -rf **/node_modules
|
||||
npm install
|
||||
rm -rf node_modules
|
||||
rm -rf **/node_modules
|
||||
corepack use yarn@4.2.2 # will update the yarn lockfile
|
||||
```
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { esbuildDecorators } from "@anatine/esbuild-decorators";
|
||||
import { build } from "esbuild";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { basename, join, posix, resolve } from "node:path";
|
||||
import { basename, join, posix, relative, resolve, sep } from "node:path";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
import {
|
||||
@@ -15,9 +15,12 @@ import { writeJSONFile } from "../src/utilities/fileSystem.js";
|
||||
import { logger } from "../src/utilities/logger.js";
|
||||
import { createTaskFileImports, gatherTaskFiles } from "../src/utilities/taskFiles.js";
|
||||
import { escapeImportPath } from "../src/utilities/windows.js";
|
||||
import { E2EJavascriptProject } from "./javascriptProject.js";
|
||||
import { PackageManager } from "../src/utilities/getUserPackageManager.js";
|
||||
|
||||
type CompileOptions = {
|
||||
outputMetafile?: string;
|
||||
packageManager: PackageManager;
|
||||
resolvedConfig: ReadConfigResult;
|
||||
tempDir: string;
|
||||
};
|
||||
@@ -28,6 +31,7 @@ export async function compile(options: CompileOptions) {
|
||||
}
|
||||
|
||||
const {
|
||||
packageManager,
|
||||
tempDir,
|
||||
resolvedConfig: { config },
|
||||
} = options;
|
||||
@@ -61,6 +65,9 @@ export async function compile(options: CompileOptions) {
|
||||
);
|
||||
}
|
||||
|
||||
const e2eJsProject = new E2EJavascriptProject(config.projectDir, packageManager);
|
||||
const directDependenciesMeta = await e2eJsProject.extractDirectDependenciesMeta();
|
||||
|
||||
const result = await build({
|
||||
stdin: {
|
||||
contents: workerContents,
|
||||
@@ -86,7 +93,12 @@ export async function compile(options: CompileOptions) {
|
||||
},
|
||||
plugins: [
|
||||
mockServerOnlyPlugin(),
|
||||
bundleDependenciesPlugin("workerFacade", config.dependenciesToBundle, config.tsconfigPath),
|
||||
bundleDependenciesPlugin(
|
||||
"workerFacade",
|
||||
directDependenciesMeta,
|
||||
config.dependenciesToBundle,
|
||||
config.tsconfigPath
|
||||
),
|
||||
workerSetupImportConfigPlugin(configPath),
|
||||
esbuildDecorators({
|
||||
tsconfig: config.tsconfigPath,
|
||||
@@ -127,7 +139,12 @@ export async function compile(options: CompileOptions) {
|
||||
__PROJECT_CONFIG__: JSON.stringify(config),
|
||||
},
|
||||
plugins: [
|
||||
bundleDependenciesPlugin("entryPoint.ts", config.dependenciesToBundle, config.tsconfigPath),
|
||||
bundleDependenciesPlugin(
|
||||
"entryPoint.ts",
|
||||
directDependenciesMeta,
|
||||
config.dependenciesToBundle,
|
||||
config.tsconfigPath
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -145,9 +162,14 @@ export async function compile(options: CompileOptions) {
|
||||
logger.debug(`Writing compiled files to ${tempDir}`);
|
||||
|
||||
// Get the metaOutput for the result build
|
||||
const pathsToProjectDir = relative(
|
||||
join(process.cwd(), "e2e", "fixtures"),
|
||||
config.projectDir
|
||||
).split(sep);
|
||||
|
||||
const metaOutput =
|
||||
result.metafile!.outputs[
|
||||
posix.join("e2e", "fixtures", basename(config.projectDir), "out", "stdin.js")
|
||||
posix.join("e2e", "fixtures", ...pathsToProjectDir, "out", "stdin.js")
|
||||
];
|
||||
|
||||
invariant(metaOutput, "Meta output for the result build is missing");
|
||||
@@ -155,7 +177,7 @@ export async function compile(options: CompileOptions) {
|
||||
// Get the metaOutput for the entryPoint build
|
||||
const entryPointMetaOutput =
|
||||
entryPointResult.metafile!.outputs[
|
||||
posix.join("e2e", "fixtures", basename(config.projectDir), "out", "stdin.js")
|
||||
posix.join("e2e", "fixtures", ...pathsToProjectDir, "out", "stdin.js")
|
||||
];
|
||||
|
||||
invariant(entryPointMetaOutput, "Meta output for the entryPoint build is missing");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface TestCase {
|
||||
resolveEnv?: { [key: string]: string };
|
||||
id: string;
|
||||
workspaceRelativeDir?: string;
|
||||
skipTypecheck?: boolean;
|
||||
wantConfigNotFoundError?: boolean;
|
||||
wantConfigInvalidError?: boolean;
|
||||
@@ -11,6 +12,16 @@ export interface TestCase {
|
||||
}
|
||||
|
||||
export const fixturesConfig: TestCase[] = [
|
||||
{
|
||||
id: "compile-monorepo-packages",
|
||||
skipTypecheck: true,
|
||||
workspaceRelativeDir: "packages/trigger",
|
||||
},
|
||||
{
|
||||
id: "compile-monorepo-packages-npm",
|
||||
skipTypecheck: true,
|
||||
workspaceRelativeDir: "packages/trigger",
|
||||
},
|
||||
{
|
||||
id: "config-infisical-sdk",
|
||||
skipTypecheck: true,
|
||||
|
||||
+2101
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "compile-monorepo-packages-npm",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.2.2+sha256.1aa43a5304405be7a7cb9cb5de7b97de9c4e8ddd3273e4dad00d6ae3eb39f0ef",
|
||||
"engines": {
|
||||
"pnpm": "8.15.5",
|
||||
"yarn": "4.2.2"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
MESSAGE: "Hello, World!",
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@compile-monorepo-packages-npm/message",
|
||||
"private": true
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@compile-monorepo-packages-npm/trigger",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@compile-monorepo-packages-npm/message": "*",
|
||||
"@trigger.dev/sdk": "3.0.0-beta.40"
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { task } from "@trigger.dev/sdk/v3";
|
||||
import { MESSAGE } from "@compile-monorepo-packages-npm/message";
|
||||
|
||||
export const helloWorldTask = task({
|
||||
id: "hello-world",
|
||||
run: async (payload) => {
|
||||
console.log(MESSAGE, payload);
|
||||
},
|
||||
});
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export const config = {
|
||||
project: "compile-monorepo-packages",
|
||||
triggerDirectories: ["./src"],
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "compile-monorepo-packages",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.15.5+sha256.4b4efa12490e5055d59b9b9fc9438b7d581a6b7af3b5675eb5c5f447cee1a589",
|
||||
"engines": {
|
||||
"pnpm": "8.15.5",
|
||||
"yarn": "4.2.2"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
MESSAGE: "Hello, World!",
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@compile-monorepo-packages/message",
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@compile-monorepo-packages/trigger",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@compile-monorepo-packages/message": "workspace:*",
|
||||
"@trigger.dev/sdk": "3.0.0-beta.40"
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { task } from "@trigger.dev/sdk/v3";
|
||||
import { MESSAGE } from "@compile-monorepo-packages/message";
|
||||
|
||||
export const helloWorldTask = task({
|
||||
id: "hello-world",
|
||||
run: async (payload) => {
|
||||
console.log(MESSAGE, payload);
|
||||
},
|
||||
});
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export const config = {
|
||||
project: "compile-monorepo-packages",
|
||||
triggerDirectories: ["./src"],
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
# https://github.com/pnpm/pnpm/issues/2412
|
||||
packages:
|
||||
- "packages/*"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,9 @@ import {
|
||||
import { ReadConfigResult } from "../src/utilities/configFiles.js";
|
||||
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";
|
||||
import { E2EJavascriptProject } from "./javascriptProject.js";
|
||||
|
||||
type HandleDependenciesOptions = {
|
||||
entryPointMetaOutput: Metafile["outputs"]["out/stdin.js"];
|
||||
@@ -25,19 +25,6 @@ type HandleDependenciesOptions = {
|
||||
tempDir: string;
|
||||
};
|
||||
|
||||
class JavascriptProjectLocal extends JavascriptProject {
|
||||
constructor(
|
||||
projectPath: string,
|
||||
private overridenPackageManager: PackageManager
|
||||
) {
|
||||
super(projectPath);
|
||||
}
|
||||
|
||||
async getPackageManager(): Promise<PackageManager> {
|
||||
return Promise.resolve(this.overridenPackageManager);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleDependencies(options: HandleDependenciesOptions) {
|
||||
if (options.resolvedConfig.status === "error") {
|
||||
throw new Error("cannot resolve config");
|
||||
@@ -58,7 +45,7 @@ export async function handleDependencies(options: HandleDependenciesOptions) {
|
||||
// Get all the required dependencies from the metaOutputs and save them to /tmp/dir/package.json
|
||||
const allImports = [...metaOutput.imports, ...entryPointMetaOutput.imports];
|
||||
|
||||
const javascriptProject = new JavascriptProjectLocal(config.projectDir, packageManager);
|
||||
const javascriptProject = new E2EJavascriptProject(config.projectDir, packageManager);
|
||||
|
||||
const dependencies = await resolveRequiredDependencies(allImports, config, javascriptProject);
|
||||
|
||||
|
||||
@@ -19,11 +19,13 @@ import { handleDependencies } from "./handleDependencies";
|
||||
import { E2EOptions, E2EOptionsSchema } from "./schemas";
|
||||
import { fixturesConfig, TestCase } from "./fixtures.config";
|
||||
import { Metafile, OutputFile } from "esbuild";
|
||||
import { findUpMultiple } from "find-up";
|
||||
|
||||
interface E2EFixtureTest extends TestCase {
|
||||
dir: string;
|
||||
fixtureDir: string;
|
||||
packageManager: PackageManager;
|
||||
tempDir: string;
|
||||
workspaceDir: string;
|
||||
}
|
||||
|
||||
const TIMEOUT = 120_000;
|
||||
@@ -49,35 +51,47 @@ logger.loggerLevel = options.logLevel;
|
||||
|
||||
if (testCases.length > 0) {
|
||||
describe.concurrent("bundling", async () => {
|
||||
beforeEach<E2EFixtureTest>(async ({ dir, packageManager, skip }) => {
|
||||
await rimraf(join(dir, "**/node_modules/**"), {
|
||||
beforeEach<E2EFixtureTest>(async ({ fixtureDir, packageManager, skip, workspaceDir }) => {
|
||||
await rimraf(join(workspaceDir, "**/node_modules"), {
|
||||
glob: true,
|
||||
});
|
||||
await rimraf(join(dir, ".yarn"), { glob: true });
|
||||
await rimraf(join(workspaceDir, ".yarn"), { glob: true });
|
||||
if (
|
||||
packageManager === "npm" &&
|
||||
(existsSync(resolve(join(dir, "yarn.lock"))) ||
|
||||
existsSync(resolve(join(dir, "yarn.lock.copy"))))
|
||||
(existsSync(resolve(join(workspaceDir, "yarn.lock"))) ||
|
||||
existsSync(resolve(join(workspaceDir, "yarn.lock.copy"))))
|
||||
) {
|
||||
// `npm ci` & `npm install` will update an existing yarn.lock
|
||||
try {
|
||||
await rename(resolve(join(dir, "yarn.lock")), resolve(join(dir, "yarn.lock.copy")));
|
||||
await rename(
|
||||
resolve(join(workspaceDir, "yarn.lock")),
|
||||
resolve(join(workspaceDir, "yarn.lock.copy"))
|
||||
);
|
||||
} catch (e) {
|
||||
await rename(resolve(join(dir, "yarn.lock.copy")), resolve(join(dir, "yarn.lock")));
|
||||
await rename(
|
||||
resolve(join(workspaceDir, "yarn.lock.copy")),
|
||||
resolve(join(workspaceDir, "yarn.lock"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.packageManager && !existsSync(resolve(dir, LOCKFILES[options.packageManager]))) {
|
||||
if (
|
||||
options.packageManager &&
|
||||
!existsSync(resolve(fixtureDir, LOCKFILES[options.packageManager]))
|
||||
) {
|
||||
skip();
|
||||
}
|
||||
|
||||
await installFixtureDeps(dir, packageManager);
|
||||
await installFixtureDeps({ fixtureDir, packageManager, workspaceDir });
|
||||
}, TIMEOUT);
|
||||
|
||||
afterEach<E2EFixtureTest>(async ({ dir, packageManager }) => {
|
||||
afterEach<E2EFixtureTest>(async ({ packageManager, workspaceDir }) => {
|
||||
if (packageManager === "npm") {
|
||||
try {
|
||||
await rename(resolve(join(dir, "yarn.lock.copy")), resolve(join(dir, "yarn.lock")));
|
||||
await rename(
|
||||
resolve(join(workspaceDir, "yarn.lock.copy")),
|
||||
resolve(join(workspaceDir, "yarn.lock"))
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -87,21 +101,24 @@ if (testCases.length > 0) {
|
||||
for (let testCase of testCases) {
|
||||
test.extend<E2EFixtureTest>({
|
||||
...testCase,
|
||||
dir: async ({ id }, use) => await use(resolve(join(process.cwd(), "e2e/fixtures", id))),
|
||||
packageManager: async ({ dir }, use) => await use(await parsePackageManager(options, dir)),
|
||||
tempDir: async ({ dir }, use) => {
|
||||
const existingTempDir = resolve(join(dir, ".trigger"));
|
||||
fixtureDir: async ({ id }, use) =>
|
||||
await use(resolve(join(process.cwd(), "e2e/fixtures", id))),
|
||||
workspaceDir: async ({ fixtureDir, workspaceRelativeDir = "" }, use) =>
|
||||
await use(resolve(join(fixtureDir, workspaceRelativeDir))),
|
||||
packageManager: async ({ workspaceDir }, use) =>
|
||||
await use(await parsePackageManager(options, workspaceDir)),
|
||||
tempDir: async ({ workspaceDir }, use) => {
|
||||
const existingTempDir = resolve(join(workspaceDir, ".trigger"));
|
||||
|
||||
if (existsSync(existingTempDir)) {
|
||||
await rm(existingTempDir, { force: true, recursive: true });
|
||||
}
|
||||
await use((await mkdir(join(dir, ".trigger"), { recursive: true })) as string);
|
||||
await use((await mkdir(join(workspaceDir, ".trigger"), { recursive: true })) as string);
|
||||
},
|
||||
})(
|
||||
`fixture '${testCase.id}'`,
|
||||
{ timeout: TIMEOUT },
|
||||
async ({
|
||||
dir,
|
||||
packageManager,
|
||||
resolveEnv,
|
||||
skipTypecheck,
|
||||
@@ -112,11 +129,12 @@ if (testCases.length > 0) {
|
||||
wantDependenciesError,
|
||||
wantInstallationError,
|
||||
wantWorkerError,
|
||||
workspaceDir,
|
||||
}) => {
|
||||
let resolvedConfig: ReadConfigResult;
|
||||
const configExpect = expect(
|
||||
(async () => {
|
||||
resolvedConfig = await readConfig(dir, { cwd: dir });
|
||||
resolvedConfig = await readConfig(workspaceDir, { cwd: workspaceDir });
|
||||
})(),
|
||||
wantConfigNotFoundError || wantConfigInvalidError
|
||||
? "does not resolve config"
|
||||
@@ -153,6 +171,7 @@ if (testCases.length > 0) {
|
||||
const compileExpect = expect(
|
||||
(async () => {
|
||||
const compilationResult = await compile({
|
||||
packageManager,
|
||||
resolvedConfig: resolvedConfig!,
|
||||
tempDir,
|
||||
});
|
||||
@@ -284,27 +303,48 @@ function debug(message: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function installFixtureDeps(dir: string, packageManager: PackageManager) {
|
||||
async function installFixtureDeps(options: {
|
||||
fixtureDir: string;
|
||||
packageManager: PackageManager;
|
||||
workspaceDir: string;
|
||||
}) {
|
||||
const { packageManager, workspaceDir } = options;
|
||||
if (["pnpm", "yarn"].includes(packageManager)) {
|
||||
const buffer = readFileSync(resolve(join(dir, "package.json")), "utf8");
|
||||
const pkgJSON = JSON.parse(buffer.toString());
|
||||
const version = pkgJSON.engines[packageManager];
|
||||
const version = await detectPackageManagerVersion(options);
|
||||
debug(`Detected ${packageManager}@${version} from package.json 'engines' field`);
|
||||
const { stdout, stderr } = await execa("corepack", ["use", `${packageManager}@${version}`], {
|
||||
cwd: dir,
|
||||
cwd: workspaceDir,
|
||||
});
|
||||
debug(stdout);
|
||||
if (stderr) console.error(stderr);
|
||||
} else {
|
||||
const { stdout, stderr } = await execa(packageManager, installArgs(packageManager), {
|
||||
cwd: dir,
|
||||
NODE_PATH: resolve(join(dir, "node_modules")),
|
||||
cwd: workspaceDir,
|
||||
NODE_PATH: resolve(join(workspaceDir, "node_modules")),
|
||||
});
|
||||
debug(stdout);
|
||||
if (stderr) console.error(stderr);
|
||||
}
|
||||
}
|
||||
|
||||
async function detectPackageManagerVersion(options: {
|
||||
fixtureDir: string;
|
||||
packageManager: PackageManager;
|
||||
workspaceDir: string;
|
||||
}): Promise<string> {
|
||||
const { fixtureDir, packageManager, workspaceDir } = options;
|
||||
const pkgPaths = await findUpMultiple("package.json", { cwd: workspaceDir, stopAt: fixtureDir });
|
||||
for (let pkgPath of pkgPaths) {
|
||||
const buffer = readFileSync(pkgPath, "utf8");
|
||||
const pkgJSON = JSON.parse(buffer.toString());
|
||||
if (!pkgJSON.engines) continue;
|
||||
const version = pkgJSON.engines[packageManager];
|
||||
if (version) return version;
|
||||
}
|
||||
|
||||
throw new Error(`No version found for package manager ${packageManager}`);
|
||||
}
|
||||
|
||||
function installArgs(packageManager: string) {
|
||||
switch (packageManager) {
|
||||
case "bun":
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { PackageManager } from "../src/utilities/getUserPackageManager.js";
|
||||
import { JavascriptProject } from "../src/utilities/javascriptProject.js";
|
||||
|
||||
export class E2EJavascriptProject extends JavascriptProject {
|
||||
constructor(
|
||||
projectPath: string,
|
||||
private overridenPackageManager: PackageManager
|
||||
) {
|
||||
super(projectPath);
|
||||
}
|
||||
|
||||
async getPackageManager(): Promise<PackageManager> {
|
||||
return Promise.resolve(this.overridenPackageManager);
|
||||
}
|
||||
}
|
||||
@@ -1170,6 +1170,9 @@ async function compileProject(
|
||||
);
|
||||
}
|
||||
|
||||
const jsProject = new JavascriptProject(config.projectDir);
|
||||
const directDependenciesMeta = await jsProject.extractDirectDependenciesMeta();
|
||||
|
||||
const result = await build({
|
||||
stdin: {
|
||||
contents: workerContents,
|
||||
@@ -1197,6 +1200,7 @@ async function compileProject(
|
||||
mockServerOnlyPlugin(),
|
||||
bundleDependenciesPlugin(
|
||||
"workerFacade",
|
||||
directDependenciesMeta,
|
||||
config.dependenciesToBundle,
|
||||
config.tsconfigPath
|
||||
),
|
||||
@@ -1253,6 +1257,7 @@ async function compileProject(
|
||||
plugins: [
|
||||
bundleDependenciesPlugin(
|
||||
"entryPoint.ts",
|
||||
directDependenciesMeta,
|
||||
config.dependenciesToBundle,
|
||||
config.tsconfigPath
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { extname, isAbsolute } from "node:path";
|
||||
import tsConfigPaths from "tsconfig-paths";
|
||||
import { logger } from "./logger";
|
||||
import { escapeImportPath } from "./windows";
|
||||
import { DependencyMeta } from "./javascriptProject";
|
||||
|
||||
export function mockServerOnlyPlugin(): Plugin {
|
||||
return {
|
||||
@@ -106,6 +107,7 @@ export function workerSetupImportConfigPlugin(configPath?: string): Plugin {
|
||||
|
||||
export function bundleDependenciesPlugin(
|
||||
buildIdentifier: string,
|
||||
dependencies: Record<string, DependencyMeta>,
|
||||
dependenciesToBundle?: Array<string | RegExp>,
|
||||
tsconfigPath?: string
|
||||
): Plugin {
|
||||
@@ -149,6 +151,10 @@ export function bundleDependenciesPlugin(
|
||||
}
|
||||
}
|
||||
|
||||
if (dependencies[args.path] && !dependencies[args.path]!.external) {
|
||||
return undefined; // let esbuild bundle it
|
||||
}
|
||||
|
||||
logger.debug(`[${buildIdentifier}] Externalizing ${args.path}`, {
|
||||
...args,
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import { recordSpanException } from "@trigger.dev/core/v3/otel";
|
||||
import { flattenAttributes } from "@trigger.dev/core/v3";
|
||||
|
||||
export type ResolveOptions = { allowDev: boolean };
|
||||
export type DependencyMeta = { version: string; external: boolean };
|
||||
|
||||
export class JavascriptProject {
|
||||
private _packageJson?: PackageJson;
|
||||
@@ -94,15 +95,39 @@ export class JavascriptProject {
|
||||
}
|
||||
}
|
||||
|
||||
async resolveAll(
|
||||
packageNames: string[],
|
||||
options?: ResolveOptions
|
||||
): Promise<Record<string, string>> {
|
||||
async extractDirectDependenciesMeta(): Promise<Record<string, DependencyMeta>> {
|
||||
return tracer.startActiveSpan(
|
||||
"JavascriptProject.extractDirectDependenciesMeta",
|
||||
async (span) => {
|
||||
const command = await this.#getCommand();
|
||||
|
||||
span.setAttributes({
|
||||
packageManager: command.name,
|
||||
});
|
||||
|
||||
try {
|
||||
span.end();
|
||||
return await command.extractDirectDependenciesMeta({
|
||||
cwd: this.projectPath,
|
||||
});
|
||||
} catch (error) {
|
||||
recordSpanException(span, error);
|
||||
span.end();
|
||||
|
||||
logger.debug(`Failed to resolve internal dependencies using ${command.name}`, {
|
||||
error,
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async resolveAll(packageNames: string[]): Promise<Record<string, string>> {
|
||||
return tracer.startActiveSpan("JavascriptProject.resolveAll", async (span) => {
|
||||
const externalPackages = packageNames.filter((packageName) => !isBuiltInModule(packageName));
|
||||
|
||||
const opts = { allowDev: false, ...options };
|
||||
|
||||
const command = await this.#getCommand();
|
||||
|
||||
span.setAttributes({
|
||||
@@ -139,18 +164,6 @@ export class JavascriptProject {
|
||||
|
||||
missingPackageVersions[packageName] = packageJsonVersion;
|
||||
}
|
||||
|
||||
if (opts.allowDev) {
|
||||
const devPackageJsonVersion = this.packageJson.devDependencies?.[packageName];
|
||||
|
||||
if (typeof devPackageJsonVersion === "string") {
|
||||
logger.debug(`Resolved ${packageName} version using devDependencies`, {
|
||||
devPackageJsonVersion,
|
||||
});
|
||||
|
||||
missingPackageVersions[packageName] = devPackageJsonVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span.setAttributes({
|
||||
@@ -271,6 +284,10 @@ interface PackageManagerCommands {
|
||||
|
||||
installDependencies(options: PackageManagerOptions): Promise<void>;
|
||||
|
||||
extractDirectDependenciesMeta(
|
||||
options: PackageManagerOptions
|
||||
): Promise<Record<string, DependencyMeta>>;
|
||||
|
||||
resolveDependencyVersion(
|
||||
packageName: string,
|
||||
options: PackageManagerOptions
|
||||
@@ -337,6 +354,46 @@ class PNPMCommands implements PackageManagerCommands {
|
||||
return results;
|
||||
}
|
||||
|
||||
async extractDirectDependenciesMeta(options: PackageManagerOptions) {
|
||||
const result = await this.#listDirectDependencies(options);
|
||||
|
||||
logger.debug(`Extracting direct dependencies metadata using ${this.name}`);
|
||||
|
||||
const results: Record<string, DependencyMeta> = {};
|
||||
|
||||
for (const projectPkg of result) {
|
||||
results[projectPkg.name] = { version: projectPkg.version, external: false };
|
||||
|
||||
if (projectPkg.dependencies) {
|
||||
for (const [name, dep] of Object.entries(projectPkg.dependencies)) {
|
||||
const { version } = dep;
|
||||
|
||||
results[name] = {
|
||||
version,
|
||||
external: !version.startsWith("link:"),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async #listDirectDependencies(options: PackageManagerOptions) {
|
||||
const childProcess = await $({
|
||||
cwd: options.cwd,
|
||||
reject: false,
|
||||
})`${this.cmd} list --recursive --json`;
|
||||
|
||||
if (childProcess.failed) {
|
||||
logger.debug("Failed to list dependencies, using stdout anyway...", {
|
||||
error: childProcess,
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.parse(childProcess.stdout) as PnpmList;
|
||||
}
|
||||
|
||||
async #listDependencies(packageNames: string[], options: PackageManagerOptions) {
|
||||
const childProcess = await $({
|
||||
cwd: options.cwd,
|
||||
@@ -345,7 +402,7 @@ class PNPMCommands implements PackageManagerCommands {
|
||||
|
||||
if (childProcess.failed) {
|
||||
logger.debug("Failed to list dependencies, using stdout anyway...", {
|
||||
error: childProcess.stderr,
|
||||
error: childProcess,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -410,6 +467,31 @@ class NPMCommands implements PackageManagerCommands {
|
||||
return results;
|
||||
}
|
||||
|
||||
async extractDirectDependenciesMeta(
|
||||
options: PackageManagerOptions
|
||||
): Promise<Record<string, DependencyMeta>> {
|
||||
const result = await this.#listDirectDependencies(options);
|
||||
|
||||
logger.debug(`Extracting direct dependencies metadata using ${this.name}`);
|
||||
|
||||
return result.dependencies ? this.#flattenDependenciesMeta(result.dependencies) : {};
|
||||
}
|
||||
|
||||
async #listDirectDependencies(options: PackageManagerOptions) {
|
||||
const childProcess = await $({
|
||||
cwd: options.cwd,
|
||||
reject: false,
|
||||
})`${this.cmd} list --json`;
|
||||
|
||||
if (childProcess.failed) {
|
||||
logger.debug("Failed to list dependencies, using stdout anyway...", {
|
||||
error: childProcess,
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.parse(childProcess.stdout) as NpmListOutput;
|
||||
}
|
||||
|
||||
async #listDependencies(packageNames: string[], options: PackageManagerOptions) {
|
||||
const childProcess = await $({
|
||||
cwd: options.cwd,
|
||||
@@ -418,7 +500,7 @@ class NPMCommands implements PackageManagerCommands {
|
||||
|
||||
if (childProcess.failed) {
|
||||
logger.debug("Failed to list dependencies, using stdout anyway...", {
|
||||
error: childProcess.stderr,
|
||||
error: childProcess,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -443,6 +525,23 @@ class NPMCommands implements PackageManagerCommands {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#flattenDependenciesMeta(
|
||||
dependencies: Record<string, NpmDependency>
|
||||
): Record<string, DependencyMeta> {
|
||||
let results: Record<string, DependencyMeta> = {};
|
||||
|
||||
for (const [name, dep] of Object.entries(dependencies)) {
|
||||
const { version, resolved, dependencies: children } = dep;
|
||||
results[name] = { version, external: !!resolved && !resolved.startsWith("file:") };
|
||||
|
||||
if (children) {
|
||||
results = { ...results, ...this.#flattenDependenciesMeta(children) };
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
class YarnCommands implements PackageManagerCommands {
|
||||
@@ -501,6 +600,39 @@ class YarnCommands implements PackageManagerCommands {
|
||||
return results;
|
||||
}
|
||||
|
||||
async extractDirectDependenciesMeta(options: PackageManagerOptions) {
|
||||
const result = await this.#listDirectDependencies(options);
|
||||
|
||||
const rawPackagesData = result.split("\n");
|
||||
logger.debug(`Extracting direct dependencies metadata using ${this.name}`);
|
||||
|
||||
const results: Record<string, DependencyMeta> = {};
|
||||
|
||||
for (const rawPackageData of rawPackagesData) {
|
||||
const packageData = JSON.parse(rawPackageData);
|
||||
|
||||
const [name, dependencyMeta] = this.#parseYarnValueIntoDependencyMeta(packageData.value);
|
||||
results[name] = dependencyMeta;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async #listDirectDependencies(options: PackageManagerOptions) {
|
||||
const childProcess = await $({
|
||||
cwd: options.cwd,
|
||||
reject: false,
|
||||
})`${this.cmd} info --all --json`;
|
||||
|
||||
if (childProcess.failed) {
|
||||
logger.debug("Failed to list dependencies, using stdout anyway...", {
|
||||
error: childProcess,
|
||||
});
|
||||
}
|
||||
|
||||
return childProcess.stdout;
|
||||
}
|
||||
|
||||
async #listDependencies(packageNames: string[], options: PackageManagerOptions) {
|
||||
const childProcess = await $({
|
||||
cwd: options.cwd,
|
||||
@@ -509,7 +641,7 @@ class YarnCommands implements PackageManagerCommands {
|
||||
|
||||
if (childProcess.failed) {
|
||||
logger.debug("Failed to list dependencies, using stdout anyway...", {
|
||||
error: childProcess.stderr,
|
||||
error: childProcess,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -531,6 +663,31 @@ class YarnCommands implements PackageManagerCommands {
|
||||
// If the value contains an "@" symbol, then the package name is the first part
|
||||
return parts[0] as string;
|
||||
}
|
||||
|
||||
#parseYarnValueIntoDependencyMeta(value: string): [string, DependencyMeta] {
|
||||
const parts = value.split("@");
|
||||
let name: string, protocol: string, version: string;
|
||||
|
||||
if (parts.length === 3) {
|
||||
// e.g. @<scope>/<package>@<protocol>:<version> -> ["", "<scope>/<package>"", "<protocol>:<version>""]
|
||||
name = `@${parts[1]}`;
|
||||
[protocol = "", version = ""] = parts[2]!.split(":");
|
||||
} else if (parts.length === 2) {
|
||||
// e.g. <package>@<protocol>:<version> -> ["<package>"", "<protocol>:<version>""]
|
||||
name = parts[0]!.toString();
|
||||
[protocol = "", version = ""] = parts[1]!.split(":");
|
||||
} else {
|
||||
throw new Error("Failed parsing ${value} into dependency meta");
|
||||
}
|
||||
|
||||
return [
|
||||
name,
|
||||
{
|
||||
version,
|
||||
external: protocol !== "workspace" && protocol !== "file",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function isBuiltInModule(module: string): boolean {
|
||||
|
||||
Reference in New Issue
Block a user