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
7 changes: 7 additions & 0 deletions .changeset/curly-falcons-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@workflow/builders": patch
"@workflow/utils": patch
"@workflow/core": patch
---

Expose workflows manifest under diagnostics folder
19 changes: 18 additions & 1 deletion packages/builders/src/base-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
import { mkdir, readFile, realpath, rename, writeFile } from 'node:fs/promises';
import { basename, dirname, join, relative, resolve } from 'node:path';
import { promisify } from 'node:util';
import { pluralize } from '@workflow/utils';
import { pluralize, usesVercelWorld } from '@workflow/utils';
import chalk from 'chalk';
import enhancedResolveOriginal from 'enhanced-resolve';
import * as esbuild from 'esbuild';
Expand Down Expand Up @@ -1063,6 +1063,16 @@ export const OPTIONS = handler;`;
return process.env.WORKFLOW_PUBLIC_MANIFEST === '1';
}

/**
* Whether diagnostics artifacts should be emitted to Vercel output.
* This is enabled when the resolved world target is Vercel.
*/
protected get shouldEmitVercelDiagnostics(): boolean {
return (
usesVercelWorld() || this.config.buildTarget === 'vercel-build-output-api'
);
}

/**
* Creates a manifest JSON file containing step/workflow/class metadata
* and graph data for visualization.
Expand Down Expand Up @@ -1096,6 +1106,13 @@ export const OPTIONS = handler;`;

await mkdir(manifestDir, { recursive: true });
await writeFile(join(manifestDir, 'manifest.json'), manifestJson);
if (this.shouldEmitVercelDiagnostics) {
const diagnosticsManifestPath = this.resolvePath(
'.vercel/output/diagnostics/workflows-manifest.json'
);
await this.ensureDirectory(diagnosticsManifestPath);
await writeFile(diagnosticsManifestPath, manifestJson);
}

const stepCount = Object.values(steps).reduce(
(acc, s) => acc + Object.keys(s).length,
Expand Down
15 changes: 13 additions & 2 deletions packages/core/e2e/local-build.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { exec as execOriginal } from 'child_process';
import { promisify } from 'util';
import { exec as execOriginal } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
import { promisify } from 'node:util';
import { describe, expect, test } from 'vitest';
import { usesVercelWorld } from '../../utils/src/world-target';
import { getWorkbenchAppPath } from './utils';

const exec = promisify(execOriginal);
Expand Down Expand Up @@ -29,5 +32,13 @@ describe.each([
});

expect(result.stderr).not.toContain('Error:');

if (usesVercelWorld()) {
const diagnosticsManifestPath = path.join(
getWorkbenchAppPath(project),
'.vercel/output/diagnostics/workflows-manifest.json'
);
await fs.access(diagnosticsManifestPath);
}
});
});
5 changes: 5 additions & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export { pluralize } from './pluralize.js';
export { once, type PromiseWithResolvers, withResolvers } from './promise.js';
export { parseDurationToDate } from './time.js';
export {
isVercelWorldTarget,
resolveWorkflowTargetWorld,
usesVercelWorld,
} from './world-target.js';
55 changes: 55 additions & 0 deletions packages/utils/src/world-target.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, expect, test } from 'vitest';
import {
isVercelWorldTarget,
resolveWorkflowTargetWorld,
usesVercelWorld,
} from './world-target.js';

describe('resolveWorkflowTargetWorld', () => {
test('returns configured world when WORKFLOW_TARGET_WORLD is set', () => {
expect(
resolveWorkflowTargetWorld({
WORKFLOW_TARGET_WORLD: '@workflow/world-postgres',
VERCEL_DEPLOYMENT_ID: 'deployment-id',
})
).toBe('@workflow/world-postgres');
});

test('defaults to vercel when VERCEL_DEPLOYMENT_ID is set', () => {
expect(
resolveWorkflowTargetWorld({
VERCEL_DEPLOYMENT_ID: 'deployment-id',
})
).toBe('vercel');
});

test('defaults to local when no world env vars are set', () => {
expect(resolveWorkflowTargetWorld({})).toBe('local');
});
});

describe('isVercelWorldTarget', () => {
test('matches vercel world targets', () => {
expect(isVercelWorldTarget('vercel')).toBe(true);
expect(isVercelWorldTarget('@workflow/world-vercel')).toBe(true);
});

test('does not match non-vercel worlds', () => {
expect(isVercelWorldTarget('local')).toBe(false);
expect(isVercelWorldTarget('@workflow/world-postgres')).toBe(false);
});
});

describe('usesVercelWorld', () => {
test('returns true for resolved vercel world', () => {
expect(
usesVercelWorld({
VERCEL_DEPLOYMENT_ID: 'deployment-id',
})
).toBe(true);
});

test('returns false for resolved local world', () => {
expect(usesVercelWorld({})).toBe(false);
});
});
22 changes: 22 additions & 0 deletions packages/utils/src/world-target.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type WorkflowEnvironment = Record<string, string | undefined>;

export function resolveWorkflowTargetWorld(
env: WorkflowEnvironment = process.env
): string {
const configuredWorld = env.WORKFLOW_TARGET_WORLD;
if (configuredWorld) {
return configuredWorld;
}

return env.VERCEL_DEPLOYMENT_ID ? 'vercel' : 'local';
}

export function isVercelWorldTarget(targetWorld: string): boolean {
return targetWorld === 'vercel' || targetWorld === '@workflow/world-vercel';
}

export function usesVercelWorld(
env: WorkflowEnvironment = process.env
): boolean {
return isVercelWorldTarget(resolveWorkflowTargetWorld(env));
}
Loading