-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
blueprint.ts
256 lines (230 loc) · 7.46 KB
/
blueprint.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import fs from 'fs'
import path from 'path'
import { BUNDLE_WRAPPER_OPEN } from './create-snapshot-script'
import { processSourceMap } from '../sourcemap/process-sourcemap'
import debug from 'debug'
import { forwardSlash } from '../utils'
const logDebug = debug('cypress:snapgen:debug')
function read (part: string, indent = ' ') {
const p = require.resolve(`../blueprint/${part}`)
const s = fs.readFileSync(p, 'utf8')
return s.split('\n').join(`\n${indent}`)
}
const globals = read('globals')
const strictGlobals = read('globals-strict')
const customRequire = read('custom-require')
const setGlobals = read('set-globals')
/**
* Configures the generation of the snapshot script from the _Blueprint_
* templates.
*
* @property processPlatform value to return during snapshot creation for `process.platform`
* @property processNodeVersion value to return during snapshot creation for `process.version`
* @property mainModuleRequirePath relative path to the module we consider the
* main entry point.
* NOTE: the {@link SnapshotDoctor} changes this in order to verify multiple
* modules using the same bundle.
* @property auxiliaryData data to embed with the snapshot
* @property customRequireDefinitions a hash of module initializer functions
* that are bundled
* @property includeStrictVerifiers see {@link GenerationOpts} includeStrictVerifiers
* @property nodeEnv see {@link GenerationOpts} nodeEnv
* @property basedir the base dir of the project for which we are creating the
* snapshot
* @property sourceMap {@link Buffer} with content of raw sourcemaps
* @property supportTypeScript see {@link GenerationOpts} supportTypeScript
*/
export type BlueprintConfig = {
processPlatform: string
processNodeVersion: string
mainModuleRequirePath: string
auxiliaryData: string
customRequireDefinitions: Buffer
includeStrictVerifiers: boolean
nodeEnv: string
basedir: string
sourceMap: Buffer | undefined
processedSourceMapPath: string | undefined
supportTypeScript: boolean
integrityCheckSource: string | undefined
}
const pathSep = path.sep === '\\' ? '\\\\' : path.sep
/**
* Generates the snapshot script from the templates found inside `./blueprint`
* and the provided {@link BlueprintConfig} and returns it a long with the
* processed sourcemaps.
*
* When rendering the snapshot script we take care of the following
* (in order of occurrence in the rendered script):
*
* 1. We embedd the path separator so that we have it available inside the
* snapshot without having to refer to the `path` module
*
* 2. We also include helper methods like `cannotAccess` which are invoked
* whenever a feature of JavaScript is accessed that would cause issues
* when snapshotting.
* We use this to throw an error during snapshot script verification in
* order to signal to the doctor that something went wrong.
* Additionally it allows us to debug any issues in a better way than
* looking at a `mksnapshot` tool Segfault.
*
* 3. Additionally we stub out a minimal version of `process` to be used
* while snapshotting
*
* 4. In order to catch all problems during snapshot verification we include
* stricter verifiers from `./blueprint/globals-strict.js` while we are
* running the doctor.
* We also set `require.isStrict = true` to query it inside
* ./blueprint/custom-require.js in order to optimize error messages for
* the specific context.
* When we create the final snapshot script which will be snapshotted we
* include less stricter versions of globals from ./blueprint/globals.js.
*/
export function scriptFromBlueprint (config: BlueprintConfig): {
script: Buffer
processedSourceMap: string | undefined
} {
const {
processPlatform,
processNodeVersion,
mainModuleRequirePath,
auxiliaryData,
customRequireDefinitions,
includeStrictVerifiers,
nodeEnv,
basedir,
sourceMap,
supportTypeScript,
integrityCheckSource,
} = config
const normalizedMainModuleRequirePath = forwardSlash(mainModuleRequirePath)
const wrapperOpen = Buffer.from(
`
(function () {
const PATH_SEP = '${pathSep}'
${integrityCheckSource || ''}
function generateSnapshot() {
//
// <process>
//
function cannotAccess(proto, prop) {
return function () {
throw 'Cannot access ' + proto + '.' + prop + ' during snapshot creation'
}
}
function getPrevent(proto, prop) {
return {
get: cannotAccess(proto, prop)
}
}
let process = {}
Object.defineProperties(process, {
platform: {
value: '${processPlatform}',
enumerable: false,
},
argv: {
value: [],
enumerable: false,
},
env: {
value: {
NODE_ENV: '${nodeEnv}',
},
enumerable: false,
},
version: {
value: '${processNodeVersion}',
enumerable: false,
},
versions: {
value: { node: '${processNodeVersion}' },
enumerable: false,
},
nextTick: getPrevent('process', 'nextTick')
})
function get_process() {
return process
}
//
// </process>
//
${globals}
${includeStrictVerifiers ? strictGlobals : ''}
`,
'utf8',
)
const wrapperClose = Buffer.from(
`
${customRequire}
${includeStrictVerifiers ? 'require.isStrict = true' : ''}
customRequire(${normalizedMainModuleRequirePath}, ${normalizedMainModuleRequirePath})
const result = {}
Object.defineProperties(result, {
customRequire: {
writable: false,
value: customRequire
},
setGlobals: {
writable: false,
value: ${setGlobals}
},
snapshotAuxiliaryData: {
writable: false,
value: ${auxiliaryData},
},
})
return result
}
let numberOfGetSnapshotResultCalls = 0
const snapshotResult = generateSnapshot.call({})
Object.defineProperties(this, {
getSnapshotResult: {
writable: false,
value: function () {
if (numberOfGetSnapshotResultCalls > 0) {
throw new Error('getSnapshotResult can only be called once')
}
numberOfGetSnapshotResultCalls++
return snapshotResult
},
},
supportTypeScript: {
writable: false,
value: ${supportTypeScript},
},
})
generateSnapshot = null
}).call(this)
`,
'utf8',
)
const buffers = [wrapperOpen, customRequireDefinitions, wrapperClose]
// Now we rendered the prelude and can calculate the bundle line offset and thus
// process and include source maps. Since it is expensive we only do this if we
// have a valid sourcemap.
let offsetToBundle: number | undefined = undefined
let processedSourceMap: string | undefined
if (sourceMap != null) {
offsetToBundle =
newLinesInBuffer(wrapperOpen) + newLinesInBuffer(BUNDLE_WRAPPER_OPEN)
processedSourceMap = processSourceMap(sourceMap, basedir, offsetToBundle)
if (processedSourceMap != null && config.processedSourceMapPath != null) {
logDebug(
'[sourcemap] writing sourcemap to "%s"',
config.processedSourceMapPath,
)
fs.writeFileSync(config.processedSourceMapPath, processedSourceMap)
}
}
return { script: Buffer.concat(buffers), processedSourceMap }
}
const CR_CODE = '\n'.charCodeAt(0)
/**
* Fast way to count new lines in a buffer.
* Converting to string and splitting on new-line would be slow.
*/
function newLinesInBuffer (buf: Buffer) {
const newLines = buf.filter((x) => x === CR_CODE)
return newLines.length
}