@jsenv/core 23.8.2 → 23.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_browser_system.js +46 -39
- package/dist/jsenv_browser_system.js.map +14 -14
- package/dist/jsenv_compile_proxy.js.map +6 -6
- package/dist/jsenv_exploring_index.js.map +5 -5
- package/dist/jsenv_exploring_redirector.js.map +12 -12
- package/dist/jsenv_toolbar.js.map +7 -7
- package/dist/jsenv_toolbar_injector.js.map +5 -5
- package/helpers/babel/.eslintrc.cjs +24 -24
- package/helpers/babel/AsyncGenerator/AsyncGenerator.js +81 -81
- package/helpers/babel/AwaitValue/AwaitValue.js +3 -3
- package/helpers/babel/applyDecoratorDescriptor/applyDecoratorDescriptor.js +33 -33
- package/helpers/babel/arrayLikeToArray/arrayLikeToArray.js +7 -7
- package/helpers/babel/arrayWithHoles/arrayWithHoles.js +4 -4
- package/helpers/babel/arrayWithoutHoles/arrayWithoutHoles.js +6 -6
- package/helpers/babel/assertThisInitialized/assertThisInitialized.js +7 -7
- package/helpers/babel/asyncGeneratorDelegate/asyncGeneratorDelegate.js +40 -40
- package/helpers/babel/asyncIterator/asyncIterator.js +12 -12
- package/helpers/babel/asyncToGenerator/asyncToGenerator.js +34 -34
- package/helpers/babel/awaitAsyncGenerator/awaitAsyncGenerator.js +5 -5
- package/helpers/babel/classApplyDescriptorDestructureSet/classApplyDescriptorDestructureSet.js +20 -20
- package/helpers/babel/classApplyDescriptorGet/classApplyDescriptorGet.js +6 -6
- package/helpers/babel/classApplyDescriptorSet/classApplyDescriptorSet.js +13 -13
- package/helpers/babel/classCallCheck/classCallCheck.js +5 -5
- package/helpers/babel/classCheckPrivateStaticAccess/classCheckPrivateStaticAccess.js +5 -5
- package/helpers/babel/classCheckPrivateStaticFieldDescriptor/classCheckPrivateStaticFieldDescriptor.js +6 -6
- package/helpers/babel/classExtractFieldDescriptor/classExtractFieldDescriptor.js +7 -7
- package/helpers/babel/classNameTDZError/classNameTDZError.js +4 -4
- package/helpers/babel/classPrivateFieldDestructureSet/classPrivateFieldDestructureSet.js +7 -7
- package/helpers/babel/classPrivateFieldGet/classPrivateFieldGet.js +7 -7
- package/helpers/babel/classPrivateFieldLooseBase/classPrivateFieldLooseBase.js +6 -6
- package/helpers/babel/classPrivateFieldLooseKey/classPrivateFieldLooseKey.js +5 -5
- package/helpers/babel/classPrivateFieldSet/classPrivateFieldSet.js +8 -8
- package/helpers/babel/classPrivateMethodGet/classPrivateMethodGet.js +6 -6
- package/helpers/babel/classPrivateMethodSet/classPrivateMethodSet.js +3 -3
- package/helpers/babel/classStaticPrivateFieldSpecGet/classStaticPrivateFieldSpecGet.js +9 -9
- package/helpers/babel/classStaticPrivateFieldSpecSet/classStaticPrivateFieldSpecSet.js +15 -15
- package/helpers/babel/classStaticPrivateMethodGet/classStaticPrivateMethodGet.js +6 -6
- package/helpers/babel/classStaticPrivateMethodSet/classStaticPrivateMethodSet.js +3 -3
- package/helpers/babel/construct/construct.js +16 -16
- package/helpers/babel/createClass/createClass.js +15 -15
- package/helpers/babel/createForOfIteratorHelper/createForOfIteratorHelper.js +60 -60
- package/helpers/babel/createForOfIteratorHelperLoose/createForOfIteratorHelperLoose.js +23 -23
- package/helpers/babel/createRawReactElement/createRawReactElement.js +50 -50
- package/helpers/babel/createSuper/createSuper.js +22 -22
- package/helpers/babel/decorate/decorate.js +403 -403
- package/helpers/babel/defaults/defaults.js +11 -11
- package/helpers/babel/defineEnumerableProperties/defineEnumerableProperties.js +23 -23
- package/helpers/babel/defineProperty/defineProperty.js +18 -18
- package/helpers/babel/extends/extends.js +14 -14
- package/helpers/babel/get/get.js +13 -13
- package/helpers/babel/getPrototypeOf/getPrototypeOf.js +4 -4
- package/helpers/babel/inherits/inherits.js +15 -15
- package/helpers/babel/inheritsLoose/inheritsLoose.js +7 -7
- package/helpers/babel/initializerDefineProperty/initializerDefineProperty.js +10 -10
- package/helpers/babel/initializerWarningHelper/initializerWarningHelper.js +6 -6
- package/helpers/babel/instanceof/instanceof.js +6 -6
- package/helpers/babel/interopRequireDefault/interopRequireDefault.js +3 -3
- package/helpers/babel/interopRequireWildcard/interopRequireWildcard.js +37 -37
- package/helpers/babel/isNativeFunction/isNativeFunction.js +4 -4
- package/helpers/babel/isNativeReflectConstruct/isNativeReflectConstruct.js +21 -21
- package/helpers/babel/iterableToArray/iterableToArray.js +7 -7
- package/helpers/babel/iterableToArrayLimit/iterableToArrayLimit.js +36 -36
- package/helpers/babel/iterableToArrayLimitLoose/iterableToArrayLimitLoose.js +10 -10
- package/helpers/babel/jsx/jsx.js +45 -45
- package/helpers/babel/maybeArrayLike/maybeArrayLike.js +10 -10
- package/helpers/babel/newArrowCheck/newArrowCheck.js +5 -5
- package/helpers/babel/nonIterableRest/nonIterableRest.js +5 -5
- package/helpers/babel/nonIterableSpread/nonIterableSpread.js +5 -5
- package/helpers/babel/objectDestructuringEmpty/objectDestructuringEmpty.js +3 -3
- package/helpers/babel/objectSpread/objectSpread.js +23 -23
- package/helpers/babel/objectSpread2/objectSpread2.js +33 -33
- package/helpers/babel/objectWithoutProperties/objectWithoutProperties.js +19 -19
- package/helpers/babel/objectWithoutPropertiesLoose/objectWithoutPropertiesLoose.js +13 -13
- package/helpers/babel/possibleConstructorReturn/possibleConstructorReturn.js +10 -10
- package/helpers/babel/readOnlyError/readOnlyError.js +4 -4
- package/helpers/babel/readme.md +9 -9
- package/helpers/babel/set/set.js +44 -44
- package/helpers/babel/setPrototypeOf/setPrototypeOf.js +6 -6
- package/helpers/babel/skipFirstGeneratorNext/skipFirstGeneratorNext.js +8 -8
- package/helpers/babel/slicedToArray/slicedToArray.js +10 -10
- package/helpers/babel/slicedToArrayLoose/slicedToArrayLoose.js +13 -13
- package/helpers/babel/superPropBase/superPropBase.js +10 -10
- package/helpers/babel/taggedTemplateLiteral/taggedTemplateLiteral.js +10 -10
- package/helpers/babel/taggedTemplateLiteralLoose/taggedTemplateLiteralLoose.js +7 -7
- package/helpers/babel/tdz/tdz.js +4 -4
- package/helpers/babel/temporalRef/temporalRef.js +6 -6
- package/helpers/babel/temporalUndefined/temporalUndefined.js +3 -3
- package/helpers/babel/toArray/toArray.js +10 -10
- package/helpers/babel/toConsumableArray/toConsumableArray.js +10 -10
- package/helpers/babel/toPrimitive/toPrimitive.js +10 -10
- package/helpers/babel/toPropertyKey/toPropertyKey.js +6 -6
- package/helpers/babel/typeof/typeof.js +14 -14
- package/helpers/babel/unsupportedIterableToArray/unsupportedIterableToArray.js +12 -12
- package/helpers/babel/wrapAsyncGenerator/wrapAsyncGenerator.js +8 -8
- package/helpers/babel/wrapNativeSuper/wrapNativeSuper.js +30 -30
- package/helpers/babel/wrapRegExp/wrapRegExp.js +63 -63
- package/helpers/babel/writeOnlyError/writeOnlyError.js +4 -4
- package/helpers/regenerator-runtime/regenerator-runtime.js +748 -748
- package/{LICENSE → license} +21 -21
- package/package.json +2 -2
- package/src/buildProject.js +300 -300
- package/src/execute.js +184 -184
- package/src/internal/browser-launcher/jsenv-browser-system.js +203 -199
- package/src/internal/building/buildUsingRollup.js +2 -10
- package/src/internal/compiling/babel_plugin_import_assertions.js +121 -121
- package/src/internal/compiling/babel_plugin_import_metadata.js +22 -22
- package/src/internal/compiling/babel_plugin_import_visitor.js +84 -84
- package/src/internal/compiling/compile-directory/getOrGenerateCompiledFile.js +268 -268
- package/src/internal/compiling/compile-directory/updateMeta.js +154 -154
- package/src/internal/compiling/compile-directory/validateCache.js +265 -265
- package/src/internal/compiling/compileFile.js +233 -224
- package/src/internal/compiling/compileHtml.js +550 -550
- package/src/internal/compiling/createCompiledFileService.js +291 -291
- package/src/internal/compiling/html_source_file_service.js +403 -404
- package/src/internal/compiling/js-compilation-service/jsenvTransform.js +272 -270
- package/src/internal/compiling/jsenvCompilerForHtml.js +374 -308
- package/src/internal/compiling/jsenvCompilerForJavaScript.js +2 -0
- package/src/internal/compiling/startCompileServer.js +1086 -1048
- package/src/internal/compiling/transformResultToCompilationResult.js +220 -220
- package/src/internal/executing/coverage/babel_plugin_instrument.js +90 -90
- package/src/internal/executing/coverage/reportToCoverage.js +193 -187
- package/src/internal/executing/executePlan.js +183 -183
- package/src/internal/executing/launchAndExecute.js +458 -458
- package/src/internal/generateGroupMap/featuresCompatMap.js +29 -0
- package/src/internal/generateGroupMap/jsenvBabelPluginCompatMap.js +1 -8
- package/src/internal/runtime/createBrowserRuntime/scanBrowserRuntimeFeatures.js +246 -246
- package/src/internal/runtime/createNodeRuntime/scanNodeRuntimeFeatures.js +112 -112
- package/src/internal/runtime/s.js +727 -727
- package/src/internal/toolbar/jsenv-logo.svg +144 -144
- package/src/internal/toolbar/toolbar.main.css +196 -196
- package/src/internal/toolbar/toolbar.main.js +227 -227
- package/src/internal/url_conversion.js +317 -317
- package/src/startExploring.js +309 -309
|
@@ -1,265 +1,265 @@
|
|
|
1
|
-
import { resolveUrl, bufferToEtag } from "@jsenv/filesystem"
|
|
2
|
-
|
|
3
|
-
import { fileURLToPath } from "node:url"
|
|
4
|
-
import { readFileSync, statSync } from "node:fs"
|
|
5
|
-
|
|
6
|
-
export const validateCache = async ({
|
|
7
|
-
compiledFileUrl,
|
|
8
|
-
compileCacheStrategy,
|
|
9
|
-
compileCacheSourcesValidation = true,
|
|
10
|
-
// When "compileCacheAssetsValidation" is enabled, code ensures that
|
|
11
|
-
// - asset file exists
|
|
12
|
-
// - asset file content matches an etag generated when the file was compiled
|
|
13
|
-
// In practice a compiled file asset is a sourcemap file or a coverage.json file.
|
|
14
|
-
// It's unlikely that something or someone would update an asset file
|
|
15
|
-
// and even when it happens it would be a bit strict to invalidate the cache
|
|
16
|
-
// so by default "compileCacheAssetsValidation" is disabled
|
|
17
|
-
// to avoid checking things for nothing
|
|
18
|
-
compileCacheAssetsValidation = false,
|
|
19
|
-
request,
|
|
20
|
-
}) => {
|
|
21
|
-
const validity = { isValid: true }
|
|
22
|
-
|
|
23
|
-
const metaJsonFileUrl = `${compiledFileUrl}__asset__meta.json`
|
|
24
|
-
const metaValidity = await validateMetaFile(metaJsonFileUrl)
|
|
25
|
-
mergeValidity(validity, "meta", metaValidity)
|
|
26
|
-
if (!validity.isValid) {
|
|
27
|
-
return validity
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const compiledFileValidation = await validateCompiledFile({
|
|
31
|
-
compiledFileUrl,
|
|
32
|
-
request,
|
|
33
|
-
compileCacheStrategy,
|
|
34
|
-
})
|
|
35
|
-
mergeValidity(validity, "compiledFile", compiledFileValidation)
|
|
36
|
-
if (!validity.isValid) {
|
|
37
|
-
return validity
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const meta = metaValidity.data
|
|
41
|
-
const [sourcesValidity, assetsValidity] = await Promise.all([
|
|
42
|
-
compileCacheSourcesValidation
|
|
43
|
-
? validateSources({
|
|
44
|
-
meta,
|
|
45
|
-
metaJsonFileUrl,
|
|
46
|
-
})
|
|
47
|
-
: { isValid: true, code: "SOURCES_VALIDATION_DISABLED" },
|
|
48
|
-
compileCacheAssetsValidation
|
|
49
|
-
? validateAssets({
|
|
50
|
-
meta,
|
|
51
|
-
metaJsonFileUrl,
|
|
52
|
-
})
|
|
53
|
-
: { isValid: true, code: "ASSETS_VALIDATION_DISABLED" },
|
|
54
|
-
])
|
|
55
|
-
mergeValidity(validity, "sources", sourcesValidity)
|
|
56
|
-
if (!validity.valid) {
|
|
57
|
-
return validity
|
|
58
|
-
}
|
|
59
|
-
mergeValidity(validity, "assets", assetsValidity)
|
|
60
|
-
|
|
61
|
-
return validity
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const mergeValidity = (parentValidity, childValidityName, childValidity) => {
|
|
65
|
-
parentValidity.isValid = childValidity.isValid
|
|
66
|
-
if (childValidity.code) parentValidity.code = childValidity.code
|
|
67
|
-
parentValidity[childValidityName] = childValidity
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const validateMetaFile = async (metaJsonFileUrl) => {
|
|
71
|
-
const validity = { isValid: true, data: {} }
|
|
72
|
-
|
|
73
|
-
let metaJsonBuffer
|
|
74
|
-
try {
|
|
75
|
-
metaJsonBuffer = readFileSync(fileURLToPath(metaJsonFileUrl))
|
|
76
|
-
} catch (error) {
|
|
77
|
-
if (error && error.code === "ENOENT") {
|
|
78
|
-
validity.isValid = false
|
|
79
|
-
validity.code = "META_FILE_NOT_FOUND"
|
|
80
|
-
return validity
|
|
81
|
-
}
|
|
82
|
-
throw error
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const metaJsonString = String(metaJsonBuffer)
|
|
86
|
-
let meta
|
|
87
|
-
try {
|
|
88
|
-
meta = JSON.parse(metaJsonString)
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if (error && error.name === "SyntaxError") {
|
|
91
|
-
validity.isValid = false
|
|
92
|
-
validity.code = "META_FILE_SYNTAX_ERROR"
|
|
93
|
-
return validity
|
|
94
|
-
}
|
|
95
|
-
throw error
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
validity.data = meta
|
|
99
|
-
if (meta.sources.length === 0) {
|
|
100
|
-
validity.isValid = false
|
|
101
|
-
validity.code = "SOURCES_EMPTY"
|
|
102
|
-
return validity
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return validity
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const validateCompiledFile = async ({
|
|
109
|
-
compiledFileUrl,
|
|
110
|
-
compileCacheStrategy,
|
|
111
|
-
request,
|
|
112
|
-
}) => {
|
|
113
|
-
const validity = { isValid: true, data: {} }
|
|
114
|
-
|
|
115
|
-
const clientCacheDisabled = request.headers["cache-control"] === "no-cache"
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
const compiledSourceBuffer = readFileSync(fileURLToPath(compiledFileUrl))
|
|
119
|
-
validity.data.compiledSourceBuffer = compiledSourceBuffer
|
|
120
|
-
|
|
121
|
-
if (!clientCacheDisabled && compileCacheStrategy === "etag") {
|
|
122
|
-
const compiledEtag = bufferToEtag(compiledSourceBuffer)
|
|
123
|
-
validity.data.compiledEtag = compiledEtag
|
|
124
|
-
const ifNoneMatch = request.headers["if-none-match"]
|
|
125
|
-
if (ifNoneMatch && ifNoneMatch !== compiledEtag) {
|
|
126
|
-
validity.isValid = false
|
|
127
|
-
validity.code = "COMPILED_FILE_ETAG_MISMATCH"
|
|
128
|
-
return validity
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!clientCacheDisabled && compileCacheStrategy === "mtime") {
|
|
133
|
-
const stats = statSync(fileURLToPath(compiledFileUrl))
|
|
134
|
-
const compiledMtime = Math.floor(stats.mtimeMs)
|
|
135
|
-
validity.data.compiledMtime = compiledMtime
|
|
136
|
-
|
|
137
|
-
const ifModifiedSince = request.headers["if-modified-since"]
|
|
138
|
-
let ifModifiedSinceDate
|
|
139
|
-
try {
|
|
140
|
-
ifModifiedSinceDate = new Date(ifModifiedSince)
|
|
141
|
-
} catch (e) {
|
|
142
|
-
ifModifiedSinceDate = null
|
|
143
|
-
// ideally we should rather respond with
|
|
144
|
-
// 400 "if-modified-since header is not a valid date"
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
ifModifiedSinceDate &&
|
|
149
|
-
ifModifiedSinceDate < dateToSecondsPrecision(compiledMtime)
|
|
150
|
-
) {
|
|
151
|
-
validity.isValid = false
|
|
152
|
-
validity.code = "COMPILED_FILE_MTIME_OUTDATED"
|
|
153
|
-
return validity
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return validity
|
|
158
|
-
} catch (error) {
|
|
159
|
-
if (error && error.code === "ENOENT") {
|
|
160
|
-
validity.isValid = false
|
|
161
|
-
validity.code = "COMPILED_FILE_NOT_FOUND"
|
|
162
|
-
return validity
|
|
163
|
-
}
|
|
164
|
-
throw error
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const validateSources = async ({ meta, metaJsonFileUrl }) => {
|
|
169
|
-
const sourcesValidity = { isValid: true }
|
|
170
|
-
await Promise.all(
|
|
171
|
-
meta.sources.map(async (source, index) => {
|
|
172
|
-
const sourceValidity = await validateSource({
|
|
173
|
-
metaJsonFileUrl,
|
|
174
|
-
source,
|
|
175
|
-
eTag: meta.sourcesEtag[index],
|
|
176
|
-
})
|
|
177
|
-
mergeValidity(sourcesValidity, source, sourceValidity)
|
|
178
|
-
}),
|
|
179
|
-
)
|
|
180
|
-
return sourcesValidity
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const validateSource = async ({ metaJsonFileUrl, source, eTag }) => {
|
|
184
|
-
const validity = { isValid: true, data: {} }
|
|
185
|
-
const sourceFileUrl = resolveUrl(source, metaJsonFileUrl)
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
const sourceBuffer = readFileSync(fileURLToPath(sourceFileUrl))
|
|
189
|
-
const sourceETag = bufferToEtag(sourceBuffer)
|
|
190
|
-
validity.data.sourceBuffer = sourceBuffer
|
|
191
|
-
validity.data.sourceETag = sourceETag
|
|
192
|
-
|
|
193
|
-
if (sourceETag !== eTag) {
|
|
194
|
-
validity.isValid = false
|
|
195
|
-
validity.code = "SOURCE_ETAG_MISMATCH"
|
|
196
|
-
return validity
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return validity
|
|
200
|
-
} catch (e) {
|
|
201
|
-
if (e && e.code === "ENOENT") {
|
|
202
|
-
// missing source invalidates the cache because
|
|
203
|
-
// we cannot check its validity
|
|
204
|
-
// HOWEVER inside writeMeta we will check if a source can be found
|
|
205
|
-
// when it cannot we will not put it as a dependency
|
|
206
|
-
// to invalidate the cache.
|
|
207
|
-
// It is important because some files are constructed on other files
|
|
208
|
-
// which are not truly on the filesystem
|
|
209
|
-
// (IN theory the above happens only for convertCommonJsWithRollup because jsenv
|
|
210
|
-
// always have a concrete file especially to avoid that kind of thing)
|
|
211
|
-
validity.isValid = false
|
|
212
|
-
validity.code = "SOURCE_NOT_FOUND"
|
|
213
|
-
return validity
|
|
214
|
-
}
|
|
215
|
-
throw e
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const validateAssets = async ({ metaJsonFileUrl, meta }) => {
|
|
220
|
-
const assetsValidity = { isValid: true }
|
|
221
|
-
await Promise.all(
|
|
222
|
-
meta.assets.map(async (asset, index) => {
|
|
223
|
-
const assetValidity = await validateAsset({
|
|
224
|
-
asset,
|
|
225
|
-
metaJsonFileUrl,
|
|
226
|
-
eTag: meta.assetsEtag[index],
|
|
227
|
-
})
|
|
228
|
-
mergeValidity(assetsValidity, asset, assetValidity)
|
|
229
|
-
}),
|
|
230
|
-
)
|
|
231
|
-
return assetsValidity
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const validateAsset = async ({ asset, metaJsonFileUrl, eTag }) => {
|
|
235
|
-
const validity = { isValid: true, data: {} }
|
|
236
|
-
const assetFileUrl = resolveUrl(asset, metaJsonFileUrl)
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
const assetBuffer = readFileSync(fileURLToPath(assetFileUrl))
|
|
240
|
-
const assetContentETag = bufferToEtag(assetBuffer)
|
|
241
|
-
validity.data.buffer = assetBuffer
|
|
242
|
-
validity.data.etag = assetContentETag
|
|
243
|
-
|
|
244
|
-
if (eTag !== assetContentETag) {
|
|
245
|
-
validity.isValid = false
|
|
246
|
-
validity.code = "ASSET_ETAG_MISMATCH"
|
|
247
|
-
return validity
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return validity
|
|
251
|
-
} catch (error) {
|
|
252
|
-
if (error && error.code === "ENOENT") {
|
|
253
|
-
validity.isValid = false
|
|
254
|
-
validity.code = "ASSET_FILE_NOT_FOUND"
|
|
255
|
-
return validity
|
|
256
|
-
}
|
|
257
|
-
throw error
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const dateToSecondsPrecision = (date) => {
|
|
262
|
-
const dateWithSecondsPrecision = new Date(date)
|
|
263
|
-
dateWithSecondsPrecision.setMilliseconds(0)
|
|
264
|
-
return dateWithSecondsPrecision
|
|
265
|
-
}
|
|
1
|
+
import { resolveUrl, bufferToEtag } from "@jsenv/filesystem"
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
import { readFileSync, statSync } from "node:fs"
|
|
5
|
+
|
|
6
|
+
export const validateCache = async ({
|
|
7
|
+
compiledFileUrl,
|
|
8
|
+
compileCacheStrategy,
|
|
9
|
+
compileCacheSourcesValidation = true,
|
|
10
|
+
// When "compileCacheAssetsValidation" is enabled, code ensures that
|
|
11
|
+
// - asset file exists
|
|
12
|
+
// - asset file content matches an etag generated when the file was compiled
|
|
13
|
+
// In practice a compiled file asset is a sourcemap file or a coverage.json file.
|
|
14
|
+
// It's unlikely that something or someone would update an asset file
|
|
15
|
+
// and even when it happens it would be a bit strict to invalidate the cache
|
|
16
|
+
// so by default "compileCacheAssetsValidation" is disabled
|
|
17
|
+
// to avoid checking things for nothing
|
|
18
|
+
compileCacheAssetsValidation = false,
|
|
19
|
+
request,
|
|
20
|
+
}) => {
|
|
21
|
+
const validity = { isValid: true }
|
|
22
|
+
|
|
23
|
+
const metaJsonFileUrl = `${compiledFileUrl}__asset__meta.json`
|
|
24
|
+
const metaValidity = await validateMetaFile(metaJsonFileUrl)
|
|
25
|
+
mergeValidity(validity, "meta", metaValidity)
|
|
26
|
+
if (!validity.isValid) {
|
|
27
|
+
return validity
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const compiledFileValidation = await validateCompiledFile({
|
|
31
|
+
compiledFileUrl,
|
|
32
|
+
request,
|
|
33
|
+
compileCacheStrategy,
|
|
34
|
+
})
|
|
35
|
+
mergeValidity(validity, "compiledFile", compiledFileValidation)
|
|
36
|
+
if (!validity.isValid) {
|
|
37
|
+
return validity
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const meta = metaValidity.data
|
|
41
|
+
const [sourcesValidity, assetsValidity] = await Promise.all([
|
|
42
|
+
compileCacheSourcesValidation
|
|
43
|
+
? validateSources({
|
|
44
|
+
meta,
|
|
45
|
+
metaJsonFileUrl,
|
|
46
|
+
})
|
|
47
|
+
: { isValid: true, code: "SOURCES_VALIDATION_DISABLED" },
|
|
48
|
+
compileCacheAssetsValidation
|
|
49
|
+
? validateAssets({
|
|
50
|
+
meta,
|
|
51
|
+
metaJsonFileUrl,
|
|
52
|
+
})
|
|
53
|
+
: { isValid: true, code: "ASSETS_VALIDATION_DISABLED" },
|
|
54
|
+
])
|
|
55
|
+
mergeValidity(validity, "sources", sourcesValidity)
|
|
56
|
+
if (!validity.valid) {
|
|
57
|
+
return validity
|
|
58
|
+
}
|
|
59
|
+
mergeValidity(validity, "assets", assetsValidity)
|
|
60
|
+
|
|
61
|
+
return validity
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const mergeValidity = (parentValidity, childValidityName, childValidity) => {
|
|
65
|
+
parentValidity.isValid = childValidity.isValid
|
|
66
|
+
if (childValidity.code) parentValidity.code = childValidity.code
|
|
67
|
+
parentValidity[childValidityName] = childValidity
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const validateMetaFile = async (metaJsonFileUrl) => {
|
|
71
|
+
const validity = { isValid: true, data: {} }
|
|
72
|
+
|
|
73
|
+
let metaJsonBuffer
|
|
74
|
+
try {
|
|
75
|
+
metaJsonBuffer = readFileSync(fileURLToPath(metaJsonFileUrl))
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error && error.code === "ENOENT") {
|
|
78
|
+
validity.isValid = false
|
|
79
|
+
validity.code = "META_FILE_NOT_FOUND"
|
|
80
|
+
return validity
|
|
81
|
+
}
|
|
82
|
+
throw error
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const metaJsonString = String(metaJsonBuffer)
|
|
86
|
+
let meta
|
|
87
|
+
try {
|
|
88
|
+
meta = JSON.parse(metaJsonString)
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error && error.name === "SyntaxError") {
|
|
91
|
+
validity.isValid = false
|
|
92
|
+
validity.code = "META_FILE_SYNTAX_ERROR"
|
|
93
|
+
return validity
|
|
94
|
+
}
|
|
95
|
+
throw error
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
validity.data = meta
|
|
99
|
+
if (meta.sources.length === 0) {
|
|
100
|
+
validity.isValid = false
|
|
101
|
+
validity.code = "SOURCES_EMPTY"
|
|
102
|
+
return validity
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return validity
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const validateCompiledFile = async ({
|
|
109
|
+
compiledFileUrl,
|
|
110
|
+
compileCacheStrategy,
|
|
111
|
+
request,
|
|
112
|
+
}) => {
|
|
113
|
+
const validity = { isValid: true, data: {} }
|
|
114
|
+
|
|
115
|
+
const clientCacheDisabled = request.headers["cache-control"] === "no-cache"
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const compiledSourceBuffer = readFileSync(fileURLToPath(compiledFileUrl))
|
|
119
|
+
validity.data.compiledSourceBuffer = compiledSourceBuffer
|
|
120
|
+
|
|
121
|
+
if (!clientCacheDisabled && compileCacheStrategy === "etag") {
|
|
122
|
+
const compiledEtag = bufferToEtag(compiledSourceBuffer)
|
|
123
|
+
validity.data.compiledEtag = compiledEtag
|
|
124
|
+
const ifNoneMatch = request.headers["if-none-match"]
|
|
125
|
+
if (ifNoneMatch && ifNoneMatch !== compiledEtag) {
|
|
126
|
+
validity.isValid = false
|
|
127
|
+
validity.code = "COMPILED_FILE_ETAG_MISMATCH"
|
|
128
|
+
return validity
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!clientCacheDisabled && compileCacheStrategy === "mtime") {
|
|
133
|
+
const stats = statSync(fileURLToPath(compiledFileUrl))
|
|
134
|
+
const compiledMtime = Math.floor(stats.mtimeMs)
|
|
135
|
+
validity.data.compiledMtime = compiledMtime
|
|
136
|
+
|
|
137
|
+
const ifModifiedSince = request.headers["if-modified-since"]
|
|
138
|
+
let ifModifiedSinceDate
|
|
139
|
+
try {
|
|
140
|
+
ifModifiedSinceDate = new Date(ifModifiedSince)
|
|
141
|
+
} catch (e) {
|
|
142
|
+
ifModifiedSinceDate = null
|
|
143
|
+
// ideally we should rather respond with
|
|
144
|
+
// 400 "if-modified-since header is not a valid date"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
ifModifiedSinceDate &&
|
|
149
|
+
ifModifiedSinceDate < dateToSecondsPrecision(compiledMtime)
|
|
150
|
+
) {
|
|
151
|
+
validity.isValid = false
|
|
152
|
+
validity.code = "COMPILED_FILE_MTIME_OUTDATED"
|
|
153
|
+
return validity
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return validity
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error && error.code === "ENOENT") {
|
|
160
|
+
validity.isValid = false
|
|
161
|
+
validity.code = "COMPILED_FILE_NOT_FOUND"
|
|
162
|
+
return validity
|
|
163
|
+
}
|
|
164
|
+
throw error
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const validateSources = async ({ meta, metaJsonFileUrl }) => {
|
|
169
|
+
const sourcesValidity = { isValid: true }
|
|
170
|
+
await Promise.all(
|
|
171
|
+
meta.sources.map(async (source, index) => {
|
|
172
|
+
const sourceValidity = await validateSource({
|
|
173
|
+
metaJsonFileUrl,
|
|
174
|
+
source,
|
|
175
|
+
eTag: meta.sourcesEtag[index],
|
|
176
|
+
})
|
|
177
|
+
mergeValidity(sourcesValidity, source, sourceValidity)
|
|
178
|
+
}),
|
|
179
|
+
)
|
|
180
|
+
return sourcesValidity
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const validateSource = async ({ metaJsonFileUrl, source, eTag }) => {
|
|
184
|
+
const validity = { isValid: true, data: {} }
|
|
185
|
+
const sourceFileUrl = resolveUrl(source, metaJsonFileUrl)
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const sourceBuffer = readFileSync(fileURLToPath(sourceFileUrl))
|
|
189
|
+
const sourceETag = bufferToEtag(sourceBuffer)
|
|
190
|
+
validity.data.sourceBuffer = sourceBuffer
|
|
191
|
+
validity.data.sourceETag = sourceETag
|
|
192
|
+
|
|
193
|
+
if (sourceETag !== eTag) {
|
|
194
|
+
validity.isValid = false
|
|
195
|
+
validity.code = "SOURCE_ETAG_MISMATCH"
|
|
196
|
+
return validity
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return validity
|
|
200
|
+
} catch (e) {
|
|
201
|
+
if (e && e.code === "ENOENT") {
|
|
202
|
+
// missing source invalidates the cache because
|
|
203
|
+
// we cannot check its validity
|
|
204
|
+
// HOWEVER inside writeMeta we will check if a source can be found
|
|
205
|
+
// when it cannot we will not put it as a dependency
|
|
206
|
+
// to invalidate the cache.
|
|
207
|
+
// It is important because some files are constructed on other files
|
|
208
|
+
// which are not truly on the filesystem
|
|
209
|
+
// (IN theory the above happens only for convertCommonJsWithRollup because jsenv
|
|
210
|
+
// always have a concrete file especially to avoid that kind of thing)
|
|
211
|
+
validity.isValid = false
|
|
212
|
+
validity.code = "SOURCE_NOT_FOUND"
|
|
213
|
+
return validity
|
|
214
|
+
}
|
|
215
|
+
throw e
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const validateAssets = async ({ metaJsonFileUrl, meta }) => {
|
|
220
|
+
const assetsValidity = { isValid: true }
|
|
221
|
+
await Promise.all(
|
|
222
|
+
meta.assets.map(async (asset, index) => {
|
|
223
|
+
const assetValidity = await validateAsset({
|
|
224
|
+
asset,
|
|
225
|
+
metaJsonFileUrl,
|
|
226
|
+
eTag: meta.assetsEtag[index],
|
|
227
|
+
})
|
|
228
|
+
mergeValidity(assetsValidity, asset, assetValidity)
|
|
229
|
+
}),
|
|
230
|
+
)
|
|
231
|
+
return assetsValidity
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const validateAsset = async ({ asset, metaJsonFileUrl, eTag }) => {
|
|
235
|
+
const validity = { isValid: true, data: {} }
|
|
236
|
+
const assetFileUrl = resolveUrl(asset, metaJsonFileUrl)
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const assetBuffer = readFileSync(fileURLToPath(assetFileUrl))
|
|
240
|
+
const assetContentETag = bufferToEtag(assetBuffer)
|
|
241
|
+
validity.data.buffer = assetBuffer
|
|
242
|
+
validity.data.etag = assetContentETag
|
|
243
|
+
|
|
244
|
+
if (eTag !== assetContentETag) {
|
|
245
|
+
validity.isValid = false
|
|
246
|
+
validity.code = "ASSET_ETAG_MISMATCH"
|
|
247
|
+
return validity
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return validity
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error && error.code === "ENOENT") {
|
|
253
|
+
validity.isValid = false
|
|
254
|
+
validity.code = "ASSET_FILE_NOT_FOUND"
|
|
255
|
+
return validity
|
|
256
|
+
}
|
|
257
|
+
throw error
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const dateToSecondsPrecision = (date) => {
|
|
262
|
+
const dateWithSecondsPrecision = new Date(date)
|
|
263
|
+
dateWithSecondsPrecision.setMilliseconds(0)
|
|
264
|
+
return dateWithSecondsPrecision
|
|
265
|
+
}
|