Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/next-root-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workflow/next": patch
---

Derive the workflow builder project root from Next.js workspace root configuration.
81 changes: 75 additions & 6 deletions packages/next/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { copyFileSync, mkdirSync, statSync } from 'node:fs';
import { copyFile, mkdir, readFile } from 'node:fs/promises';
import { dirname, isAbsolute, join } from 'node:path';
import { dirname, isAbsolute, join, resolve } from 'node:path';
import type { NextConfig } from 'next';
import semver from 'semver';
import { getNextBuilder } from './builder.js';
Expand All @@ -16,7 +16,6 @@ const VERCEL_WORLD_SERVER_EXTERNAL_PACKAGES = [
VERCEL_WORLD_PACKAGE,
...VERCEL_WORLD_DEPENDENCY_PACKAGES,
];

const useWorkflowPattern = /^\s*(['"])use workflow\1;?\s*$/m;
const useStepPattern = /^\s*(['"])use step\1;?\s*$/m;
const workflowSerdeImportPattern = /from\s+(['"])@workflow\/serde\1/;
Expand Down Expand Up @@ -232,6 +231,74 @@ function fileExists(path: string): boolean {
}
}

function findRootFile(names: string[], workingDir: string): string | undefined {
let current = resolve(workingDir);

while (true) {
for (const name of names) {
const file = join(current, name);
if (fileExists(file)) {
return file;
}
}

const parent = dirname(current);
if (parent === current) {
return undefined;
}
current = parent;
}
}

function findNextRootFile(workingDir: string): string | undefined {
return (
findRootFile(['pnpm-workspace.yaml'], workingDir) ??
findRootFile(
[
'pnpm-lock.yaml',
'package-lock.json',
'yarn.lock',
'bun.lock',
'bun.lockb',
],
workingDir
)
);
}

function resolveNextProjectRoot(
nextConfig: NextConfig,
workingDir: string
): string {
const configuredRoot =
nextConfig.outputFileTracingRoot ?? nextConfig.turbopack?.root;

if (configuredRoot) {
return isAbsolute(configuredRoot)
? configuredRoot
: resolve(workingDir, configuredRoot);
}

let rootFile = findNextRootFile(workingDir);
if (!rootFile) {
return workingDir;
}

while (true) {
const currentDir = dirname(rootFile);
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
return currentDir;
}

const parentRootFile = findNextRootFile(parentDir);
if (!parentRootFile) {
return currentDir;
}
rootFile = parentRootFile;
}
}

function getWorkflowManifestCopyPaths({
projectDir,
distDir,
Expand Down Expand Up @@ -442,7 +509,9 @@ export function withWorkflow(
nextConfig.turbopack.rules = {};
}
const existingRules = nextConfig.turbopack.rules as any;
const nextVersion = resolveNextVersion(process.cwd());
const workingDir = process.cwd();
const nextVersion = resolveNextVersion(workingDir);
const projectRoot = resolveNextProjectRoot(nextConfig, workingDir);
const supportsTurboCondition = semver.gte(nextVersion, 'v16.0.0');

const shouldWatch = process.env.NODE_ENV === 'development';
Expand Down Expand Up @@ -474,9 +543,9 @@ export function withWorkflow(
'jsx',
'js',
],
projectRoot: nextConfig.outputFileTracingRoot,
moduleSpecifierRoot: process.cwd(),
workingDir: process.cwd(),
projectRoot,
moduleSpecifierRoot: workingDir,
workingDir,
distDir,
diagnosticsDir: `${distDir}/diagnostics`,
buildTarget: 'next',
Expand Down
Loading