mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
169 lines
6.2 KiB
TypeScript
169 lines
6.2 KiB
TypeScript
/**
|
|
* Shared utilities for detecting files that need workflow transformation.
|
|
* Used by builders, rollup plugin, Next.js loader, and vite plugin.
|
|
*/
|
|
|
|
// Matches: 'use workflow'; "use workflow"; 'use step'; "use step"; at line start with optional whitespace
|
|
export const useWorkflowPattern = /^\s*(['"])use workflow\1;?\s*$/m;
|
|
export const useStepPattern = /^\s*(['"])use step\1;?\s*$/m;
|
|
|
|
// Matches imports from '@workflow/serde' (for custom class serialization)
|
|
// e.g.: import { WORKFLOW_SERIALIZE } from '@workflow/serde';
|
|
export const workflowSerdeImportPattern = /from\s+(['"])@workflow\/serde\1/;
|
|
|
|
// Matches direct usage of Symbol.for('workflow-serialize') or Symbol.for('workflow-deserialize')
|
|
// e.g.: static [Symbol.for('workflow-serialize')](instance) { ... }
|
|
export const workflowSerdeSymbolPattern =
|
|
/Symbol\.for\s*\(\s*(['"])workflow-(?:serialize|deserialize)\1\s*\)/;
|
|
|
|
// Matches usage of WORKFLOW_SERIALIZE or WORKFLOW_DESERIALIZE as computed property names
|
|
// e.g.: static [WORKFLOW_SERIALIZE](instance) { ... }
|
|
// This catches cases where the symbols are imported and used (even if bundled/re-exported)
|
|
export const workflowSerdeComputedPropertyPattern =
|
|
/\[\s*WORKFLOW_(?:SERIALIZE|DESERIALIZE)\s*\]/;
|
|
|
|
const templateLiteralPattern = /`(?:\\[\s\S]|[^`\\])*`/g;
|
|
const commentPattern = /\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g;
|
|
const directiveLinePattern = /^\s*(['"])(use workflow|use step)\1;?\s*$/;
|
|
const stringDirectiveLinePattern = /^\s*(['"])[^'"]+\1;?\s*$/;
|
|
|
|
// Pattern to detect generated workflow route files that should be excluded
|
|
// These files are generated by the build process and should not be re-processed
|
|
export const generatedWorkflowPathPattern =
|
|
/[/\\]\.well-known[/\\]workflow[/\\]/;
|
|
|
|
function hasDirective(source: string, directive: 'use workflow' | 'use step') {
|
|
let previousMeaningfulLine: string | undefined;
|
|
|
|
for (const line of source.split(/\r?\n/)) {
|
|
const trimmedLine = line.trim();
|
|
if (trimmedLine === '') {
|
|
continue;
|
|
}
|
|
|
|
const directiveMatch = directiveLinePattern.exec(trimmedLine);
|
|
if (directiveMatch) {
|
|
if (
|
|
directiveMatch[2] === directive &&
|
|
(previousMeaningfulLine === undefined ||
|
|
previousMeaningfulLine.endsWith('{') ||
|
|
stringDirectiveLinePattern.test(previousMeaningfulLine))
|
|
) {
|
|
return true;
|
|
}
|
|
previousMeaningfulLine = trimmedLine;
|
|
continue;
|
|
}
|
|
|
|
previousMeaningfulLine = trimmedLine;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Detects workflow-related patterns in source code.
|
|
*/
|
|
export interface WorkflowPatternMatch {
|
|
/** File contains 'use workflow' directive */
|
|
hasUseWorkflow: boolean;
|
|
/** File contains 'use step' directive */
|
|
hasUseStep: boolean;
|
|
/** File contains @workflow/serde import */
|
|
hasSerdeImport: boolean;
|
|
/** File contains Symbol.for('workflow-serialize') or Symbol.for('workflow-deserialize') */
|
|
hasSerdeSymbol: boolean;
|
|
/** File contains any directive ('use workflow' or 'use step') */
|
|
hasDirective: boolean;
|
|
/** File contains any serde pattern (import or Symbol.for) */
|
|
hasSerde: boolean;
|
|
}
|
|
|
|
/**
|
|
* Detects workflow-related patterns in source code.
|
|
* @param source - The source code to analyze
|
|
* @returns Object with flags for each detected pattern
|
|
*/
|
|
export function detectWorkflowPatterns(source: string): WorkflowPatternMatch {
|
|
const hasDirectiveSubstring =
|
|
source.includes('use workflow') || source.includes('use step');
|
|
const sourceForDirectives =
|
|
hasDirectiveSubstring && (source.includes('`') || source.includes('/'))
|
|
? source
|
|
.replace(templateLiteralPattern, (match) =>
|
|
match.replace(/[^\r\n]/g, ' ')
|
|
)
|
|
.replace(commentPattern, (match) => match.replace(/[^\r\n]/g, ' '))
|
|
: source;
|
|
const hasUseWorkflow =
|
|
source.includes('use workflow') &&
|
|
hasDirective(sourceForDirectives, 'use workflow');
|
|
const hasUseStep =
|
|
source.includes('use step') &&
|
|
hasDirective(sourceForDirectives, 'use step');
|
|
const hasSerdeImport =
|
|
source.includes('@workflow/serde') &&
|
|
workflowSerdeImportPattern.test(source);
|
|
const hasSerdeSymbol =
|
|
(source.includes('workflow-serialize') ||
|
|
source.includes('workflow-deserialize')) &&
|
|
workflowSerdeSymbolPattern.test(source);
|
|
const hasSerdeComputedProperty =
|
|
(source.includes('WORKFLOW_SERIALIZE') ||
|
|
source.includes('WORKFLOW_DESERIALIZE')) &&
|
|
workflowSerdeComputedPropertyPattern.test(source);
|
|
|
|
return {
|
|
hasUseWorkflow,
|
|
hasUseStep,
|
|
hasSerdeImport,
|
|
hasSerdeSymbol,
|
|
hasDirective: hasUseWorkflow || hasUseStep,
|
|
hasSerde: hasSerdeImport || hasSerdeSymbol || hasSerdeComputedProperty,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Determines if a file path is a generated workflow route file.
|
|
* @param filePath - The file path to check
|
|
* @returns true if the file is a generated workflow route
|
|
*/
|
|
export function isGeneratedWorkflowFile(filePath: string): boolean {
|
|
return generatedWorkflowPathPattern.test(filePath);
|
|
}
|
|
|
|
/**
|
|
* Determines if a file should be transformed based on its path and content patterns.
|
|
*
|
|
* Logic:
|
|
* - Generated workflow route files are never transformed
|
|
* - Files with directives ('use workflow' or 'use step') are always transformed
|
|
* - Files with serde patterns are always transformed; the SWC plugin performs
|
|
* AST-level extraction/filtering to determine whether they actually define
|
|
* serde classes
|
|
*
|
|
* @param filePath - The file path to check
|
|
* @param patterns - The detected patterns from detectWorkflowPatterns()
|
|
* @returns true if the file should be transformed
|
|
*/
|
|
export function shouldTransformFile(
|
|
filePath: string,
|
|
patterns: WorkflowPatternMatch
|
|
): boolean {
|
|
// Never transform generated workflow route files
|
|
if (isGeneratedWorkflowFile(filePath)) {
|
|
return false;
|
|
}
|
|
|
|
return patterns.hasDirective || patterns.hasSerde;
|
|
}
|
|
|
|
/**
|
|
* Combined regex pattern for turbopack content matching.
|
|
* Uses backreferences to ensure matching quote types.
|
|
* Matches: 'use workflow', 'use step', @workflow/serde imports, Symbol.for serialization symbols,
|
|
* and WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE computed property usage
|
|
*/
|
|
export const turbopackContentPattern =
|
|
/(use workflow|use step|from\s+(['"])@workflow\/serde\2|Symbol\.for\s*\(\s*(['"])workflow-(?:serialize|deserialize)\3\s*\)|\[\s*WORKFLOW_(?:SERIALIZE|DESERIALIZE)\s*\])/;
|