@sdeverywhere/plugin-check 0.3.19 → 0.3.21
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/bin/sde-check.js +8 -14
- package/dist/index.cjs +289 -109
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -5
- package/dist/index.d.ts +48 -5
- package/dist/index.js +275 -95
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/template-bundle/src/bundle-model-runner.ts +159 -0
- package/template-bundle/src/bundle.ts +20 -73
- package/template-bundle/src/empty-model-spec.ts +4 -0
- package/template-bundle/src/impl-vars.ts +59 -0
- package/template-report/index.html +22 -0
- package/template-report/src/{baselines → bundles}/unused.txt +1 -1
- package/template-report/src/env.d.ts +3 -1
- package/template-report/src/index.ts +289 -84
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Bundle, ConfigInitOptions, SuiteSummary } from '@sdeverywhere/check-core'
|
|
4
4
|
|
|
5
|
+
import type { BundleLocation, BundleSpec } from '@sdeverywhere/check-ui-shell'
|
|
5
6
|
import { initAppShell } from '@sdeverywhere/check-ui-shell'
|
|
6
7
|
import '@sdeverywhere/check-ui-shell/dist/style.css'
|
|
7
8
|
|
|
@@ -14,72 +15,39 @@ import { createBundle as createBaselineBundle } from '@_baseline_bundle_'
|
|
|
14
15
|
import { createBundle as createCurrentBundle } from '@_current_bundle_'
|
|
15
16
|
import { getConfigOptions } from '@_test_config_'
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} else {
|
|
21
|
-
return false
|
|
22
|
-
}
|
|
18
|
+
interface BundleMetadata {
|
|
19
|
+
name: string
|
|
20
|
+
url: string
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
function
|
|
23
|
+
function loadBundleMetadata(side: 'left' | 'right'): BundleMetadata | undefined {
|
|
26
24
|
if (import.meta.hot) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const metadataJson = localStorage.getItem(`sde-check-selected-bundle-${side}`)
|
|
26
|
+
if (metadataJson) {
|
|
27
|
+
const parsed = JSON.parse(metadataJson)
|
|
28
|
+
if (parsed.name && parsed.url) {
|
|
29
|
+
return parsed as BundleMetadata
|
|
30
|
+
}
|
|
31
|
+
}
|
|
30
32
|
}
|
|
33
|
+
return undefined
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
function
|
|
36
|
+
function saveBundleMetadata(side: 'left' | 'right', metadata: BundleMetadata): void {
|
|
34
37
|
if (import.meta.hot) {
|
|
35
|
-
localStorage.setItem(`sde-check-selected-bundle-${
|
|
38
|
+
localStorage.setItem(`sde-check-selected-bundle-${side}`, JSON.stringify(metadata))
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
// For local development mode,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
type LoadBundle = () => Promise<Bundle>
|
|
44
|
-
const availableBundles: { [key: string]: LoadBundle } = {}
|
|
45
|
-
let bundleNames: string[]
|
|
46
|
-
let selectedBaselineBundleName: string
|
|
47
|
-
let selectedCurrentBundleName: string
|
|
42
|
+
// For local development mode, use the bundle metadata saved in `LocalStorage`
|
|
43
|
+
let savedBundleMetadataL: BundleMetadata | undefined
|
|
44
|
+
let savedBundleMetadataR: BundleMetadata | undefined
|
|
48
45
|
// The following value will be injected by `vite-config-for-report.ts`
|
|
49
|
-
const
|
|
50
|
-
if (import.meta.hot &&
|
|
46
|
+
const bundlesPath = './bundles/*.txt'
|
|
47
|
+
if (import.meta.hot && bundlesPath) {
|
|
51
48
|
// Restore the previously selected bundles (from before the page was reloaded)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// Get the available baseline bundles. The glob pattern part will be replaced
|
|
56
|
-
// by Vite (see `vite-config-for-report.ts`). Note that we provide a placeholder
|
|
57
|
-
// here that looks like a valid glob pattern, since Vite's dependency resolver will
|
|
58
|
-
// report errors if it is invalid (not a literal).
|
|
59
|
-
const bundlesGlob = import.meta.glob('./baselines/*.txt', {
|
|
60
|
-
eager: false
|
|
61
|
-
})
|
|
62
|
-
const baselineBundleNames: string[] = []
|
|
63
|
-
for (const bundleKey of Object.keys(bundlesGlob)) {
|
|
64
|
-
const loadBundle = bundlesGlob[bundleKey]
|
|
65
|
-
const bundlePathParts = bundleKey.split('/')
|
|
66
|
-
const bundleFileName = bundlePathParts[bundlePathParts.length - 1]
|
|
67
|
-
const bundleName = bundleFileName.replace('.js', '')
|
|
68
|
-
baselineBundleNames.push(bundleName)
|
|
69
|
-
availableBundles[bundleName] = async () => {
|
|
70
|
-
const module = (await loadBundle()) as BundleModule
|
|
71
|
-
return module.createBundle() as Bundle
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Alphabetize (reversed, so that newer dates are at the top of the list)
|
|
76
|
-
baselineBundleNames.sort((a, b) => {
|
|
77
|
-
return b.toLowerCase().localeCompare(a.toLowerCase())
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
// Always include the "current" bundle as the first option, followed by
|
|
81
|
-
// the alphabetized bundle names
|
|
82
|
-
bundleNames = ['current', ...baselineBundleNames]
|
|
49
|
+
savedBundleMetadataL = loadBundleMetadata('left')
|
|
50
|
+
savedBundleMetadataR = loadBundleMetadata('right')
|
|
83
51
|
}
|
|
84
52
|
|
|
85
53
|
async function initForProduction(): Promise<void> {
|
|
@@ -122,41 +90,119 @@ async function initForProduction(): Promise<void> {
|
|
|
122
90
|
}
|
|
123
91
|
|
|
124
92
|
async function initForLocal(): Promise<void> {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
93
|
+
interface BundleResult {
|
|
94
|
+
bundle: Bundle
|
|
95
|
+
bundleName: string
|
|
96
|
+
bundleUrl: string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function createBundle(bundleMetadata: BundleMetadata | undefined): Promise<BundleResult> {
|
|
100
|
+
if (bundleMetadata === undefined) {
|
|
101
|
+
bundleMetadata = {
|
|
102
|
+
name: 'current',
|
|
103
|
+
url: 'current'
|
|
104
|
+
}
|
|
128
105
|
}
|
|
129
106
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
107
|
+
if (bundleMetadata.url.startsWith('http')) {
|
|
108
|
+
// Load remote bundles using dynamic import
|
|
109
|
+
try {
|
|
110
|
+
// Add cache busting parameter
|
|
111
|
+
const cacheBuster = `?cb=${Date.now()}`
|
|
112
|
+
const fullUrl = `${bundleMetadata.url}${cacheBuster}`
|
|
113
|
+
const module = await import(/* @vite-ignore */ fullUrl)
|
|
114
|
+
const bundle = module.createBundle() as Bundle
|
|
115
|
+
return {
|
|
116
|
+
bundle,
|
|
117
|
+
bundleName: bundleMetadata.name,
|
|
118
|
+
bundleUrl: bundleMetadata.url
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.error(
|
|
122
|
+
`ERROR: Failed to load remote bundle from ${bundleMetadata.url}; will use "current" bundle instead. Cause:`,
|
|
123
|
+
e
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
} else if (bundleMetadata.url.startsWith('file://')) {
|
|
127
|
+
// Load local bundles using `import.meta.glob` (since dynamic import isn't
|
|
128
|
+
// available for file URLs due to security restrictions). The glob pattern
|
|
129
|
+
// part will be replaced by Vite (see `vite-config-for-report.ts`). Note
|
|
130
|
+
// that we provide a placeholder here that looks like a valid glob pattern,
|
|
131
|
+
// since Vite's dependency resolver will report errors if it is invalid
|
|
132
|
+
// (not a literal).
|
|
133
|
+
try {
|
|
134
|
+
const bundlesGlob = import.meta.glob('./bundles/*.txt', {
|
|
135
|
+
eager: false
|
|
136
|
+
})
|
|
137
|
+
const remoteBundleUrlParts = bundleMetadata.url.split('/')
|
|
138
|
+
const remoteBundleFileName = remoteBundleUrlParts[remoteBundleUrlParts.length - 1]
|
|
139
|
+
const bundleKey = Object.keys(bundlesGlob).find(key => {
|
|
140
|
+
const bundlePathParts = key.split('/')
|
|
141
|
+
const bundleFileName = bundlePathParts[bundlePathParts.length - 1]
|
|
142
|
+
return bundleFileName === remoteBundleFileName
|
|
143
|
+
})
|
|
144
|
+
if (bundleKey) {
|
|
145
|
+
type BundleModule = {
|
|
146
|
+
createBundle(): Bundle
|
|
147
|
+
}
|
|
148
|
+
const loadBundle = bundlesGlob[bundleKey]
|
|
149
|
+
const module = (await loadBundle()) as BundleModule
|
|
150
|
+
const bundle = module.createBundle() as Bundle
|
|
151
|
+
return {
|
|
152
|
+
bundle,
|
|
153
|
+
bundleName: bundleMetadata.name,
|
|
154
|
+
bundleUrl: bundleMetadata.url
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.error(
|
|
159
|
+
`ERROR: Failed to load local bundle from ${bundleMetadata.url}; will use "current" bundle instead. Cause:`,
|
|
160
|
+
e
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Load the "current" bundle if it was requested or if the other loading
|
|
166
|
+
// processes failed
|
|
167
|
+
const bundle = createCurrentBundle()
|
|
168
|
+
return {
|
|
169
|
+
bundle,
|
|
170
|
+
bundleName: 'current',
|
|
171
|
+
bundleUrl: 'current'
|
|
142
172
|
}
|
|
143
|
-
return [bundle, bundleName]
|
|
144
173
|
}
|
|
145
174
|
|
|
146
|
-
const
|
|
147
|
-
const
|
|
175
|
+
const { bundle: bundleL, bundleName: bundleNameL, bundleUrl: bundleUrlL } = await createBundle(savedBundleMetadataL)
|
|
176
|
+
const { bundle: bundleR, bundleName: bundleNameR, bundleUrl: bundleUrlR } = await createBundle(savedBundleMetadataR)
|
|
148
177
|
|
|
149
178
|
// Prepare the model check/comparison configuration
|
|
150
179
|
const configInitOptions: ConfigInitOptions = {
|
|
151
180
|
bundleNameL,
|
|
152
|
-
bundleNameR
|
|
153
|
-
simplifyScenarios: loadSimplifyScenariosFlag()
|
|
181
|
+
bundleNameR
|
|
154
182
|
}
|
|
155
183
|
const configOptions = await getConfigOptions(bundleL, bundleR, configInitOptions)
|
|
156
184
|
|
|
185
|
+
// Override the concurrency setting using the value from LocalStorage
|
|
186
|
+
const concurrencyValue = localStorage.getItem('sde-check-concurrency')
|
|
187
|
+
if (concurrencyValue !== null) {
|
|
188
|
+
const concurrency = parseInt(concurrencyValue)
|
|
189
|
+
configOptions.concurrency = !isNaN(concurrency) ? concurrency : 1
|
|
190
|
+
} else {
|
|
191
|
+
configOptions.concurrency = 1
|
|
192
|
+
}
|
|
193
|
+
|
|
157
194
|
// Initialize the root Svelte component
|
|
195
|
+
const remoteBundlesUrl = __REMOTE_BUNDLES_URL__
|
|
158
196
|
initAppShell(configOptions, {
|
|
159
|
-
|
|
197
|
+
bundleSelectorConfig: {
|
|
198
|
+
bundleUrlL,
|
|
199
|
+
bundleUrlR,
|
|
200
|
+
remoteBundlesUrl: remoteBundlesUrl !== '' ? remoteBundlesUrl : undefined,
|
|
201
|
+
getLocalBundles: import.meta.hot ? getLocalBundles : undefined,
|
|
202
|
+
downloadBundle: import.meta.hot ? downloadBundle : undefined,
|
|
203
|
+
copyBundle: import.meta.hot ? copyBundle : undefined,
|
|
204
|
+
onBundlesChanged: import.meta.hot ? onBundlesChanged : undefined
|
|
205
|
+
}
|
|
160
206
|
})
|
|
161
207
|
}
|
|
162
208
|
|
|
@@ -188,21 +234,180 @@ if (import.meta.hot) {
|
|
|
188
234
|
document.addEventListener('sde-check-bundle', e => {
|
|
189
235
|
// Change the selected bundle
|
|
190
236
|
const info = (e as CustomEvent).detail
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
237
|
+
const bundleMetadata = { name: info.name, url: info.url }
|
|
238
|
+
if (info.side === 'left') {
|
|
239
|
+
saveBundleMetadata('left', bundleMetadata)
|
|
240
|
+
savedBundleMetadataL = bundleMetadata
|
|
241
|
+
} else if (info.side === 'right') {
|
|
242
|
+
saveBundleMetadata('right', bundleMetadata)
|
|
243
|
+
savedBundleMetadataR = bundleMetadata
|
|
197
244
|
}
|
|
198
245
|
|
|
199
246
|
// Reinitialize using the chosen bundles
|
|
200
247
|
initBundlesAndUI()
|
|
201
248
|
})
|
|
202
249
|
|
|
203
|
-
// Reload everything when the user
|
|
204
|
-
|
|
205
|
-
|
|
250
|
+
// Reload everything when the user applies updated configuration (e.g., updated filters or
|
|
251
|
+
// concurrency setting)
|
|
252
|
+
document.addEventListener('sde-check-config-changed', () => {
|
|
253
|
+
// Reinitialize using the new configuration
|
|
206
254
|
initBundlesAndUI()
|
|
207
255
|
})
|
|
208
256
|
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get the list of locally available bundles (only in development mode with HMR enbaled).
|
|
260
|
+
*/
|
|
261
|
+
async function getLocalBundles(): Promise<BundleLocation[]> {
|
|
262
|
+
// Only available in development mode with HMR
|
|
263
|
+
if (!import.meta.hot) {
|
|
264
|
+
throw new Error('getLocalBundles is only available in development mode with HMR enabled')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return new Promise((resolve, reject) => {
|
|
268
|
+
// Set up listeners
|
|
269
|
+
const handleSuccess = (data: { bundles: Array<{ name: string; url: string; lastModified: string }> }) => {
|
|
270
|
+
cleanup()
|
|
271
|
+
|
|
272
|
+
// Add the bundles that were found in the Node process
|
|
273
|
+
const bundles: BundleLocation[] = data.bundles.map(b => ({
|
|
274
|
+
name: b.name,
|
|
275
|
+
url: b.url,
|
|
276
|
+
lastModified: b.lastModified
|
|
277
|
+
}))
|
|
278
|
+
|
|
279
|
+
// Add the special "current" bundle that is generated by the builder
|
|
280
|
+
const currentBundleLastModified = __CURRENT_BUNDLE_LAST_MODIFIED__
|
|
281
|
+
bundles.push({
|
|
282
|
+
name: 'current',
|
|
283
|
+
url: 'current',
|
|
284
|
+
lastModified: currentBundleLastModified
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
resolve(bundles)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const handleError = (data: { error: string }) => {
|
|
291
|
+
cleanup()
|
|
292
|
+
reject(new Error(data.error))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const cleanup = () => {
|
|
296
|
+
import.meta.hot.off('list-bundles-success', handleSuccess)
|
|
297
|
+
import.meta.hot.off('list-bundles-error', handleError)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
import.meta.hot.on('list-bundles-success', handleSuccess)
|
|
301
|
+
import.meta.hot.on('list-bundles-error', handleError)
|
|
302
|
+
|
|
303
|
+
// Send request to list bundles
|
|
304
|
+
import.meta.hot.send('list-bundles', {})
|
|
305
|
+
|
|
306
|
+
// Timeout after 5 seconds
|
|
307
|
+
setTimeout(() => {
|
|
308
|
+
cleanup()
|
|
309
|
+
reject(new Error('Timeout waiting for bundle list'))
|
|
310
|
+
}, 5000)
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Download a bundle from the network (only in development mode with HMR).
|
|
316
|
+
*/
|
|
317
|
+
function downloadBundle(bundle: BundleSpec): void {
|
|
318
|
+
// Only available in development mode with HMR
|
|
319
|
+
if (!import.meta.hot) {
|
|
320
|
+
throw new Error('downloadBundle is only available in development mode with HMR enabled')
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!bundle.remote) {
|
|
324
|
+
throw new Error('Only bundles with a remote URL can be downloaded')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const { url, name, lastModified } = bundle.remote
|
|
328
|
+
|
|
329
|
+
// Set up listeners for download result
|
|
330
|
+
const handleSuccess = (data: { name: string; filePath: string }) => {
|
|
331
|
+
cleanup()
|
|
332
|
+
if (data.name === name) {
|
|
333
|
+
console.log(`Successfully downloaded bundle: ${name} to ${data.filePath}`)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const handleError = (data: { name: string; error: string }) => {
|
|
338
|
+
cleanup()
|
|
339
|
+
if (data.name === name) {
|
|
340
|
+
console.error(`Failed to download bundle ${name}:`, data.error)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const cleanup = () => {
|
|
345
|
+
import.meta.hot.off('download-bundle-success', handleSuccess)
|
|
346
|
+
import.meta.hot.off('download-bundle-error', handleError)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
import.meta.hot.on('download-bundle-success', handleSuccess)
|
|
350
|
+
import.meta.hot.on('download-bundle-error', handleError)
|
|
351
|
+
|
|
352
|
+
// Send download request
|
|
353
|
+
import.meta.hot.send('download-bundle', { url, name, lastModified })
|
|
354
|
+
|
|
355
|
+
console.log(`Requesting download of bundle: ${name} from ${url}`)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Copy a local bundle file to a new name.
|
|
360
|
+
*/
|
|
361
|
+
function copyBundle(bundle: BundleSpec, newName: string): void {
|
|
362
|
+
// Only available in development mode with HMR
|
|
363
|
+
if (!import.meta.hot) {
|
|
364
|
+
throw new Error('copyBundle is only available in development mode with HMR enabled')
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!bundle.local) {
|
|
368
|
+
throw new Error('Only local bundles can be copied')
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const { url, name } = bundle.local
|
|
372
|
+
|
|
373
|
+
// Set up listeners for copy result
|
|
374
|
+
const handleSuccess = (data: { name: string; filePath: string }) => {
|
|
375
|
+
cleanup()
|
|
376
|
+
if (data.name === newName) {
|
|
377
|
+
console.log(`Successfully copied bundle: ${name} to ${data.filePath}`)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const handleError = (data: { name: string; error: string }) => {
|
|
382
|
+
cleanup()
|
|
383
|
+
if (data.name === name) {
|
|
384
|
+
console.error(`Failed to copy bundle ${name}:`, data.error)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const cleanup = () => {
|
|
389
|
+
import.meta.hot.off('copy-bundle-success', handleSuccess)
|
|
390
|
+
import.meta.hot.off('copy-bundle-error', handleError)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
import.meta.hot.on('copy-bundle-success', handleSuccess)
|
|
394
|
+
import.meta.hot.on('copy-bundle-error', handleError)
|
|
395
|
+
|
|
396
|
+
// Send copy request
|
|
397
|
+
import.meta.hot.send('copy-bundle', { url, name, newName })
|
|
398
|
+
|
|
399
|
+
console.log(`Requesting copy of bundle: ${name} to ${newName}`)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Add a listener that is notified when there are file system changes detected in the
|
|
404
|
+
* local bundles directory.
|
|
405
|
+
*/
|
|
406
|
+
function onBundlesChanged(listener: () => void): void {
|
|
407
|
+
// Only available in development mode with HMR
|
|
408
|
+
if (!import.meta.hot) {
|
|
409
|
+
throw new Error('onBundlesChanged is only available in development mode with HMR enabled')
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
import.meta.hot.on('bundles-changed', listener)
|
|
413
|
+
}
|