@@ -75,10 +75,13 @@ import {
75
75
WorkerOptions ,
76
76
} from './worker-options' ;
77
77
import { WorkflowCodecRunner } from './workflow-codec-runner' ;
78
- import { WorkflowBundleWithSourceMap , WorkflowCodeBundler } from './workflow/bundler' ;
78
+ import { WorkflowBundle , WorkflowCodeBundler } from './workflow/bundler' ;
79
79
import { Workflow , WorkflowCreator } from './workflow/interface' ;
80
80
import { ThreadedVMWorkflowCreator } from './workflow/threaded-vm' ;
81
81
import { VMWorkflowCreator } from './workflow/vm' ;
82
+ import type { RawSourceMap } from 'source-map' ;
83
+ import * as vm from 'node:vm' ;
84
+ import * as path from 'node:path' ;
82
85
83
86
import IWorkflowActivationJob = coresdk . workflow_activation . IWorkflowActivationJob ;
84
87
import EvictionReason = coresdk . workflow_activation . RemoveFromCache . EvictionReason ;
@@ -145,13 +148,9 @@ export interface WorkerConstructor {
145
148
create (
146
149
connection : NativeConnection ,
147
150
options : CompiledWorkerOptions ,
148
- bundle ?: WorkflowBundleWithSourceMap
149
- ) : Promise < NativeWorkerLike > ;
150
- createReplay (
151
- options : CompiledWorkerOptions ,
152
- history : History ,
153
- bundle : WorkflowBundleWithSourceMap
151
+ bundle ?: WorkflowBundle
154
152
) : Promise < NativeWorkerLike > ;
153
+ createReplay ( options : CompiledWorkerOptions , history : History , bundle : WorkflowBundle ) : Promise < NativeWorkerLike > ;
155
154
}
156
155
157
156
function isOptionsWithBuildId < T extends CompiledWorkerOptions > ( options : T ) : options is T & { buildId : string } {
@@ -180,7 +179,7 @@ export class NativeWorker implements NativeWorkerLike {
180
179
public static async create (
181
180
connection : NativeConnection ,
182
181
options : CompiledWorkerOptions ,
183
- bundle ?: WorkflowBundleWithSourceMap
182
+ bundle ?: WorkflowBundle
184
183
) : Promise < NativeWorkerLike > {
185
184
const runtime = Runtime . instance ( ) ;
186
185
const nativeWorker = await runtime . registerWorker (
@@ -193,7 +192,7 @@ export class NativeWorker implements NativeWorkerLike {
193
192
public static async createReplay (
194
193
options : CompiledWorkerOptions ,
195
194
history : History ,
196
- bundle : WorkflowBundleWithSourceMap
195
+ bundle : WorkflowBundle
197
196
) : Promise < NativeWorkerLike > {
198
197
const runtime = Runtime . instance ( ) ;
199
198
const nativeWorker = await runtime . createReplayWorker ( addBuildIdIfMissing ( options , bundle . code ) , history ) ;
@@ -410,9 +409,8 @@ export class Worker {
410
409
...( compiledOptions . workflowBundle && isCodeBundleOption ( compiledOptions . workflowBundle )
411
410
? {
412
411
// Avoid dumping workflow bundle code to the console
413
- workflowBundle : < WorkflowBundleWithSourceMap > {
412
+ workflowBundle : < WorkflowBundle > {
414
413
code : `<string of length ${ compiledOptions . workflowBundle . code . length } >` ,
415
- sourceMap : `<string of length ${ compiledOptions . workflowBundle . sourceMap . length } >` ,
416
414
} ,
417
415
}
418
416
: { } ) ,
@@ -441,14 +439,14 @@ export class Worker {
441
439
}
442
440
443
441
protected static async createWorkflowCreator (
444
- bundle : WorkflowBundleWithSourceMap ,
442
+ workflowBundle : WorkflowBundleWithSourceMap ,
445
443
compiledOptions : CompiledWorkerOptions
446
444
) : Promise < WorkflowCreator > {
447
445
if ( compiledOptions . debugMode ) {
448
- return await VMWorkflowCreator . create ( bundle . code , bundle . sourceMap , compiledOptions . isolateExecutionTimeoutMs ) ;
446
+ return await VMWorkflowCreator . create ( workflowBundle , compiledOptions . isolateExecutionTimeoutMs ) ;
449
447
} else {
450
448
return await ThreadedVMWorkflowCreator . create ( {
451
- ... bundle ,
449
+ workflowBundle ,
452
450
threadPoolSize : compiledOptions . workflowThreadPoolSize ,
453
451
isolateExecutionTimeoutMs : compiledOptions . isolateExecutionTimeoutMs ,
454
452
} ) ;
@@ -560,20 +558,17 @@ export class Worker {
560
558
} ) ;
561
559
const bundle = await bundler . createBundle ( ) ;
562
560
logger . info ( 'Workflow bundle created' , { size : `${ toMB ( bundle . code . length ) } MB` } ) ;
563
- return bundle ;
561
+ return parseWorkflowCode ( bundle . code ) ;
564
562
} else if ( compiledOptions . workflowBundle ) {
565
563
if ( compiledOptions . bundlerOptions ) {
566
564
throw new ValueError ( `You cannot set both WorkerOptions.workflowBundle and .bundlerOptions` ) ;
567
565
}
568
566
569
567
if ( isCodeBundleOption ( compiledOptions . workflowBundle ) ) {
570
- return compiledOptions . workflowBundle ;
568
+ return parseWorkflowCode ( compiledOptions . workflowBundle . code ) ;
571
569
} else if ( isPathBundleOption ( compiledOptions . workflowBundle ) ) {
572
- const [ code , sourceMap ] = await Promise . all ( [
573
- fs . readFile ( compiledOptions . workflowBundle . codePath , 'utf8' ) ,
574
- fs . readFile ( compiledOptions . workflowBundle . sourceMapPath , 'utf8' ) ,
575
- ] ) ;
576
- return { code, sourceMap } ;
570
+ const code = await fs . readFile ( compiledOptions . workflowBundle . codePath , 'utf8' ) ;
571
+ return parseWorkflowCode ( code , compiledOptions . workflowBundle . codePath ) ;
577
572
} else {
578
573
throw new TypeError ( 'Invalid WorkflowOptions.workflowBundle' ) ;
579
574
}
@@ -1649,6 +1644,51 @@ export class Worker {
1649
1644
}
1650
1645
}
1651
1646
1647
+ export interface WorkflowBundleWithSourceMap {
1648
+ code : string ;
1649
+ sourceMap : RawSourceMap ;
1650
+ filename : string ;
1651
+ }
1652
+
1653
+ export function parseWorkflowCode ( code : string , codePath ?: string ) : WorkflowBundleWithSourceMap {
1654
+ const sourceMappingUrlDataRegex = / \s * \n [ / ] [ / ] [ # ] \s + s o u r c e M a p p i n g U R L = d a t a : (?: [ ^ , ] * ; ) b a s e 6 4 , ( [ 0 - 9 A - Z a - z + / = ] + ) \s * $ / ;
1655
+ const sourceMapMatcher = code . match ( sourceMappingUrlDataRegex ) ;
1656
+ if ( ! sourceMapMatcher ) throw new Error ( "Can't extract inlined source map from the provided Workflow Bundle" ) ;
1657
+
1658
+ const sourceMapJson = Buffer . from ( sourceMapMatcher [ 1 ] , 'base64' ) . toString ( ) ;
1659
+ const sourceMap : RawSourceMap = JSON . parse ( sourceMapJson ) ;
1660
+
1661
+ // JS debuggers (at least VSCode's) have a few requirements regarding the script and its source map, notably:
1662
+ // - The script file name's must look like an absolute path (relative paths are treated as node internals scripts)
1663
+ // - If the script contains a sourceMapURL directive, the executable 'file' indicated by the source map must match the
1664
+ // filename of the script itself. If the source map's file is a relative path, then it gets resolved relative to cwd
1665
+ const filename = path . resolve ( process . cwd ( ) , codePath ?? sourceMap . file ) ;
1666
+ if ( filename !== codePath ) {
1667
+ sourceMap . file = filename ;
1668
+ const patchedSourceMapJson = Buffer . from ( JSON . stringify ( sourceMap ) ) . toString ( 'base64' ) ;
1669
+ const fixedSourceMappingUrl = `\n//# sourceMappingURL=data:application/json;base64,${ patchedSourceMapJson } ` ;
1670
+ code = code . slice ( 0 , - sourceMapMatcher [ 1 ] . length ) + fixedSourceMappingUrl ;
1671
+ }
1672
+
1673
+ // Preloading the script makes breakpoints significantly more reliable and more responsive
1674
+ let script : vm . Script | undefined = new vm . Script ( code , { filename } ) ;
1675
+ let context : any = vm . createContext ( { } ) ;
1676
+ try {
1677
+ script . runInContext ( context ) ;
1678
+ } catch ( e ) {
1679
+ // Context has not been properly configured, so eventual errors are possible. Just ignore at this point
1680
+ }
1681
+
1682
+ // Keep these objects from GC long enough for debugger to complete parsing the source map and reporting locations
1683
+ // to the node process. Otherwise, the debugger risks source mapping resolution errors, meaning breakpoints wont work.
1684
+ setTimeout ( ( ) => {
1685
+ script = undefined ;
1686
+ context = undefined ;
1687
+ } , 10000 ) ;
1688
+
1689
+ return { code, sourceMap, filename } ;
1690
+ }
1691
+
1652
1692
type NonNullableObject < T > = { [ P in keyof T ] -?: NonNullable < T [ P ] > } ;
1653
1693
1654
1694
/**
0 commit comments