@chainlink/cre-sdk 1.6.0-alpha.2 → 1.6.0-alpha.4
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/README.md +7 -1
- package/bin/cre-compile.ts +41 -12
- package/dist/sdk/report.js +0 -15
- package/package.json +3 -3
- package/scripts/run.ts +6 -1
- package/scripts/src/check-determinism.test.ts +64 -0
- package/scripts/src/check-determinism.ts +32 -0
- package/scripts/src/compile-cli-args.test.ts +32 -0
- package/scripts/src/compile-cli-args.ts +35 -0
- package/scripts/src/compile-to-js.test.ts +90 -0
- package/scripts/src/compile-to-js.ts +53 -7
- package/scripts/src/compile-to-wasm.ts +11 -5
- package/scripts/src/compile-workflow.ts +60 -13
- package/scripts/src/generate-chain-selectors.ts +9 -27
- package/scripts/src/typecheck-workflow.test.ts +77 -0
- package/scripts/src/typecheck-workflow.ts +96 -0
- package/scripts/src/validate-shared.ts +400 -0
- package/scripts/src/validate-workflow-determinism.test.ts +409 -0
- package/scripts/src/validate-workflow-determinism.ts +545 -0
- package/scripts/src/validate-workflow-runtime-compat.ts +25 -377
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { checkWorkflowDeterminism } from './validate-workflow-determinism'
|
|
6
|
+
|
|
7
|
+
let tempDir: string
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tempDir = mkdtempSync(path.join(tmpdir(), 'cre-determinism-test-'))
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
/** Write a file in the temp directory and return its absolute path. */
|
|
18
|
+
const writeTemp = (filename: string, content: string): string => {
|
|
19
|
+
const filePath = path.join(tempDir, filename)
|
|
20
|
+
const dir = path.dirname(filePath)
|
|
21
|
+
mkdirSync(dir, { recursive: true })
|
|
22
|
+
writeFileSync(filePath, content, 'utf-8')
|
|
23
|
+
return filePath
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Assert that the analyzer returns warnings matching the given patterns. */
|
|
27
|
+
const expectWarnings = (entryPath: string, expectedPatterns: (string | RegExp)[]) => {
|
|
28
|
+
const warnings = checkWorkflowDeterminism(entryPath)
|
|
29
|
+
expect(warnings.length).toBeGreaterThan(0)
|
|
30
|
+
const combined = warnings.map((w) => w.message).join('\n')
|
|
31
|
+
for (const pattern of expectedPatterns) {
|
|
32
|
+
if (typeof pattern === 'string') {
|
|
33
|
+
expect(combined).toContain(pattern)
|
|
34
|
+
} else {
|
|
35
|
+
expect(combined).toMatch(pattern)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Assert that the analyzer returns NO warnings. */
|
|
41
|
+
const expectNoWarnings = (entryPath: string) => {
|
|
42
|
+
const warnings = checkWorkflowDeterminism(entryPath)
|
|
43
|
+
expect(warnings).toEqual([])
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Promise.race() / Promise.any()
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
describe('Promise.race(), Promise.any(), and Promise.all()', () => {
|
|
51
|
+
test('detects Promise.race()', () => {
|
|
52
|
+
const entry = writeTemp(
|
|
53
|
+
'workflow.ts',
|
|
54
|
+
`const result = await Promise.race([Promise.resolve(1), Promise.resolve(2)]);\n`,
|
|
55
|
+
)
|
|
56
|
+
expectWarnings(entry, ['Promise.race() is non-deterministic'])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('detects globalThis.Promise.race()', () => {
|
|
60
|
+
const entry = writeTemp(
|
|
61
|
+
'workflow.ts',
|
|
62
|
+
`const result = await globalThis.Promise.race([Promise.resolve(1), Promise.resolve(2)]);\n`,
|
|
63
|
+
)
|
|
64
|
+
expectWarnings(entry, ['Promise.race() is non-deterministic'])
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('detects Promise.any()', () => {
|
|
68
|
+
const entry = writeTemp(
|
|
69
|
+
'workflow.ts',
|
|
70
|
+
`const result = await Promise.any([Promise.resolve(1), Promise.resolve(2)]);\n`,
|
|
71
|
+
)
|
|
72
|
+
expectWarnings(entry, ['Promise.any() is non-deterministic'])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('detects Promise.all()', () => {
|
|
76
|
+
const entry = writeTemp(
|
|
77
|
+
'workflow.ts',
|
|
78
|
+
`const results = await Promise.all([Promise.resolve(1), Promise.resolve(2)]);\n`,
|
|
79
|
+
)
|
|
80
|
+
expectWarnings(entry, ['Promise.all() executes promises concurrently'])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('detects globalThis.Promise.all()', () => {
|
|
84
|
+
const entry = writeTemp(
|
|
85
|
+
'workflow.ts',
|
|
86
|
+
`const results = await globalThis.Promise.all([Promise.resolve(1), Promise.resolve(2)]);\n`,
|
|
87
|
+
)
|
|
88
|
+
expectWarnings(entry, ['Promise.all() executes promises concurrently'])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('does NOT flag Promise.allSettled()', () => {
|
|
92
|
+
const entry = writeTemp(
|
|
93
|
+
'workflow.ts',
|
|
94
|
+
`const results = await Promise.allSettled([Promise.resolve(1)]);\n`,
|
|
95
|
+
)
|
|
96
|
+
expectNoWarnings(entry)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('does NOT flag user-defined object named Promise with race method', () => {
|
|
100
|
+
const entry = writeTemp(
|
|
101
|
+
'workflow.ts',
|
|
102
|
+
`const Promise = { race: (x: any) => x };\nPromise.race([1, 2]);\n`,
|
|
103
|
+
)
|
|
104
|
+
expectNoWarnings(entry)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('does NOT flag property access obj.race()', () => {
|
|
108
|
+
const entry = writeTemp('workflow.ts', `const obj = { race: () => 42 };\nobj.race();\n`)
|
|
109
|
+
expectNoWarnings(entry)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Date.now() / new Date()
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
describe('Date.now() and new Date()', () => {
|
|
118
|
+
test('detects Date.now()', () => {
|
|
119
|
+
const entry = writeTemp('workflow.ts', `const ts = Date.now();\n`)
|
|
120
|
+
expectWarnings(entry, ['Date.now() uses the system clock'])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('detects globalThis.Date.now()', () => {
|
|
124
|
+
const entry = writeTemp('workflow.ts', `const ts = globalThis.Date.now();\n`)
|
|
125
|
+
expectWarnings(entry, ['Date.now() uses the system clock'])
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('detects new Date() with no arguments', () => {
|
|
129
|
+
const entry = writeTemp('workflow.ts', `const d = new Date();\n`)
|
|
130
|
+
expectWarnings(entry, ['new Date() without arguments uses the system clock'])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('detects new globalThis.Date() with no arguments', () => {
|
|
134
|
+
const entry = writeTemp('workflow.ts', `const d = new globalThis.Date();\n`)
|
|
135
|
+
expectWarnings(entry, ['new Date() without arguments uses the system clock'])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('detects new Date without parens', () => {
|
|
139
|
+
const entry = writeTemp('workflow.ts', `const d = new Date;\n`)
|
|
140
|
+
expectWarnings(entry, ['new Date() without arguments uses the system clock'])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('does NOT flag new Date(timestamp)', () => {
|
|
144
|
+
const entry = writeTemp('workflow.ts', `const d = new Date(1700000000000);\n`)
|
|
145
|
+
expectNoWarnings(entry)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('does NOT flag new Date(string)', () => {
|
|
149
|
+
const entry = writeTemp('workflow.ts', `const d = new Date('2024-01-01');\n`)
|
|
150
|
+
expectNoWarnings(entry)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('does NOT flag user-defined Date class', () => {
|
|
154
|
+
const entry = writeTemp(
|
|
155
|
+
'workflow.ts',
|
|
156
|
+
`class Date { static now() { return 42; } }\nDate.now();\n`,
|
|
157
|
+
)
|
|
158
|
+
expectNoWarnings(entry)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('does NOT flag user-defined Date variable with now method', () => {
|
|
162
|
+
const entry = writeTemp('workflow.ts', `const Date = { now: () => 42 };\nDate.now();\n`)
|
|
163
|
+
expectNoWarnings(entry)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('does NOT let an inner block Date shadow suppress outer Date.now()', () => {
|
|
167
|
+
const entry = writeTemp(
|
|
168
|
+
'workflow.ts',
|
|
169
|
+
`if (true) { const Date = { now: () => 42 }; }\nconst ts = Date.now();\n`,
|
|
170
|
+
)
|
|
171
|
+
expectWarnings(entry, ['Date.now() uses the system clock'])
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('does NOT flag Date.now() when Date is shadowed in the active block', () => {
|
|
175
|
+
const entry = writeTemp(
|
|
176
|
+
'workflow.ts',
|
|
177
|
+
`if (true) { const Date = { now: () => 42 };\n Date.now();\n}\n`,
|
|
178
|
+
)
|
|
179
|
+
expectNoWarnings(entry)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('does NOT flag globalThis.Date.now() when globalThis is shadowed locally', () => {
|
|
183
|
+
const entry = writeTemp(
|
|
184
|
+
'workflow.ts',
|
|
185
|
+
`const globalThis = { Date: { now: () => 42 } };\nglobalThis.Date.now();\n`,
|
|
186
|
+
)
|
|
187
|
+
expectNoWarnings(entry)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('warns when Date.now() appears before the local Date declaration in the same scope', () => {
|
|
191
|
+
const entry = writeTemp(
|
|
192
|
+
'workflow.ts',
|
|
193
|
+
`const ts = Date.now();\nconst Date = { now: () => 42 };\n`,
|
|
194
|
+
)
|
|
195
|
+
expectWarnings(entry, ['Date.now() uses the system clock'])
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// for...in loops
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
describe('for...in loops', () => {
|
|
204
|
+
test('detects for...in loop', () => {
|
|
205
|
+
const entry = writeTemp(
|
|
206
|
+
'workflow.ts',
|
|
207
|
+
`const obj = { a: 1, b: 2 };\nfor (const key in obj) { console.log(key); }\n`,
|
|
208
|
+
)
|
|
209
|
+
expectWarnings(entry, ['for...in loop iteration order is not guaranteed'])
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
test('does NOT flag for...of loop', () => {
|
|
213
|
+
const entry = writeTemp(
|
|
214
|
+
'workflow.ts',
|
|
215
|
+
`const arr = [1, 2, 3];\nfor (const val of arr) { console.log(val); }\n`,
|
|
216
|
+
)
|
|
217
|
+
expectNoWarnings(entry)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test('does NOT flag regular for loop', () => {
|
|
221
|
+
const entry = writeTemp('workflow.ts', `for (let i = 0; i < 10; i++) { console.log(i); }\n`)
|
|
222
|
+
expectNoWarnings(entry)
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Object.keys/values/entries() without .sort()
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
describe('Object.keys/values/entries() without .sort()', () => {
|
|
231
|
+
test('detects Object.keys() without sort', () => {
|
|
232
|
+
const entry = writeTemp('workflow.ts', `const keys = Object.keys({ a: 1, b: 2 });\n`)
|
|
233
|
+
expectWarnings(entry, ['Object.keys() returns items in an order that may vary'])
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('detects Object.values() without sort', () => {
|
|
237
|
+
const entry = writeTemp('workflow.ts', `const vals = Object.values({ a: 1, b: 2 });\n`)
|
|
238
|
+
expectWarnings(entry, ['Object.values() returns items in an order that may vary'])
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test('detects Object.entries() without sort', () => {
|
|
242
|
+
const entry = writeTemp('workflow.ts', `const entries = Object.entries({ a: 1, b: 2 });\n`)
|
|
243
|
+
expectWarnings(entry, ['Object.entries() returns items in an order that may vary'])
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test('detects globalThis.Object.keys() without sort', () => {
|
|
247
|
+
const entry = writeTemp('workflow.ts', `const keys = globalThis.Object.keys({ a: 1, b: 2 });\n`)
|
|
248
|
+
expectWarnings(entry, ['Object.keys() returns items in an order that may vary'])
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
test('does NOT flag Object.keys().sort()', () => {
|
|
252
|
+
const entry = writeTemp('workflow.ts', `const keys = Object.keys({ a: 1, b: 2 }).sort();\n`)
|
|
253
|
+
expectNoWarnings(entry)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('does NOT flag Object.entries().sort()', () => {
|
|
257
|
+
const entry = writeTemp(
|
|
258
|
+
'workflow.ts',
|
|
259
|
+
`const entries = Object.entries({ a: 1, b: 2 }).sort();\n`,
|
|
260
|
+
)
|
|
261
|
+
expectNoWarnings(entry)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test('does NOT flag Object.keys().toSorted()', () => {
|
|
265
|
+
const entry = writeTemp('workflow.ts', `const keys = Object.keys({ a: 1, b: 2 }).toSorted();\n`)
|
|
266
|
+
expectNoWarnings(entry)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('does NOT flag Object.keys().filter().sort()', () => {
|
|
270
|
+
const entry = writeTemp(
|
|
271
|
+
'workflow.ts',
|
|
272
|
+
`const keys = Object.keys({ a: 1, b: 2 }).filter(k => k !== 'a').sort();\n`,
|
|
273
|
+
)
|
|
274
|
+
expectNoWarnings(entry)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
test('does NOT flag Object.keys().map().filter().sort()', () => {
|
|
278
|
+
const entry = writeTemp(
|
|
279
|
+
'workflow.ts',
|
|
280
|
+
`const keys = Object.keys({ a: 1, b: 2 }).map(k => k.toUpperCase()).filter(k => k !== 'A').sort();\n`,
|
|
281
|
+
)
|
|
282
|
+
expectNoWarnings(entry)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test('detects Object.keys().map() (no sort)', () => {
|
|
286
|
+
const entry = writeTemp(
|
|
287
|
+
'workflow.ts',
|
|
288
|
+
`const mapped = Object.keys({ a: 1, b: 2 }).map(k => k.toUpperCase());\n`,
|
|
289
|
+
)
|
|
290
|
+
expectWarnings(entry, ['Object.keys() returns items in an order that may vary'])
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
test('detects Object.keys().filter() without sort', () => {
|
|
294
|
+
const entry = writeTemp(
|
|
295
|
+
'workflow.ts',
|
|
296
|
+
`const filtered = Object.keys({ a: 1, b: 2 }).filter(k => k !== 'a');\n`,
|
|
297
|
+
)
|
|
298
|
+
expectWarnings(entry, ['Object.keys() returns items in an order that may vary'])
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test('does NOT flag user-defined Object variable', () => {
|
|
302
|
+
const entry = writeTemp(
|
|
303
|
+
'workflow.ts',
|
|
304
|
+
`const Object = { keys: (x: any) => [] };\nObject.keys({ a: 1 });\n`,
|
|
305
|
+
)
|
|
306
|
+
expectNoWarnings(entry)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test('does NOT flag Object.freeze() or other safe methods', () => {
|
|
310
|
+
const entry = writeTemp(
|
|
311
|
+
'workflow.ts',
|
|
312
|
+
`const frozen = Object.freeze({ a: 1, b: 2 });\nconst assigned = Object.assign({}, { a: 1 });\n`,
|
|
313
|
+
)
|
|
314
|
+
expectNoWarnings(entry)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
test('warns when Object.keys() appears before the local Object declaration in the same scope', () => {
|
|
318
|
+
const entry = writeTemp(
|
|
319
|
+
'workflow.ts',
|
|
320
|
+
`const keys = Object.keys({ a: 1 });\nconst Object = { keys: (x: any) => [] as string[] };\n`,
|
|
321
|
+
)
|
|
322
|
+
expectWarnings(entry, ['Object.keys() returns items in an order that may vary'])
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
// Safe patterns (should NOT warn)
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
describe('safe patterns', () => {
|
|
331
|
+
test('does NOT flag Math.random()', () => {
|
|
332
|
+
const entry = writeTemp('workflow.ts', `const r = Math.random();\n`)
|
|
333
|
+
expectNoWarnings(entry)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
test('clean workflow produces no warnings', () => {
|
|
337
|
+
const entry = writeTemp(
|
|
338
|
+
'workflow.ts',
|
|
339
|
+
`
|
|
340
|
+
const data = { z: 1, a: 2, m: 3 };
|
|
341
|
+
const sortedKeys = Object.keys(data).sort();
|
|
342
|
+
for (const key of sortedKeys) {
|
|
343
|
+
console.log(key);
|
|
344
|
+
}
|
|
345
|
+
const results = await Promise.resolve([1, 2]);
|
|
346
|
+
const d = new Date(1700000000000);
|
|
347
|
+
`,
|
|
348
|
+
)
|
|
349
|
+
expectNoWarnings(entry)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
test('empty file produces no warnings', () => {
|
|
353
|
+
const entry = writeTemp('workflow.ts', '')
|
|
354
|
+
expectNoWarnings(entry)
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
// Transitive analysis
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
|
|
362
|
+
describe('transitive analysis', () => {
|
|
363
|
+
test('detects warnings in transitively imported files', () => {
|
|
364
|
+
writeTemp('helper.ts', `export const getTime = () => Date.now();\n`)
|
|
365
|
+
const entry = writeTemp(
|
|
366
|
+
'workflow.ts',
|
|
367
|
+
`import { getTime } from './helper';\nconsole.log(getTime());\n`,
|
|
368
|
+
)
|
|
369
|
+
expectWarnings(entry, ['Date.now() uses the system clock'])
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
test('reports multiple warnings from multiple files', () => {
|
|
373
|
+
writeTemp('helper.ts', `export const racePromises = () => Promise.race([]);\n`)
|
|
374
|
+
const entry = writeTemp(
|
|
375
|
+
'workflow.ts',
|
|
376
|
+
`import { racePromises } from './helper';\nconst d = new Date();\n`,
|
|
377
|
+
)
|
|
378
|
+
expectWarnings(entry, ['Promise.race() is non-deterministic', 'new Date() without arguments'])
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// Output format
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
describe('output format', () => {
|
|
387
|
+
test('warnings include file path and line/column info', () => {
|
|
388
|
+
const entry = writeTemp('workflow.ts', `const d = new Date();\n`)
|
|
389
|
+
const warnings = checkWorkflowDeterminism(entry)
|
|
390
|
+
expect(warnings.length).toBe(1)
|
|
391
|
+
expect(warnings[0].filePath).toContain('workflow.ts')
|
|
392
|
+
expect(warnings[0].line).toBe(1)
|
|
393
|
+
expect(warnings[0].column).toBeGreaterThan(0)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
test('warnings are returned as array (not thrown)', () => {
|
|
397
|
+
const entry = writeTemp(
|
|
398
|
+
'workflow.ts',
|
|
399
|
+
`Promise.race([]);\nDate.now();\nfor (const k in {}) {}\n`,
|
|
400
|
+
)
|
|
401
|
+
const warnings = checkWorkflowDeterminism(entry)
|
|
402
|
+
expect(warnings.length).toBe(3)
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
test('non-existent entry file returns no warnings', () => {
|
|
406
|
+
const nonExistent = path.join(tempDir, 'does-not-exist.ts')
|
|
407
|
+
expectNoWarnings(nonExistent)
|
|
408
|
+
})
|
|
409
|
+
})
|