polendina 3.3.3 → 3.3.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/AGENTS.md CHANGED
@@ -65,6 +65,9 @@ Real usage, in increasing order of involvement. Each shape must keep working:
65
65
  - `polendina --runner=bare-async test/test.js` paired with
66
66
  `polendina-node bare-async test/test.js`. The same files run in Node.js and
67
67
  the browser with no test framework at all.
68
+ - `import hex from '!!raw-loader!./fixtures/block.hex'` inside a test to read a
69
+ fixture as a string. Binary and hex fixture suites do this, declaring no
70
+ loader of their own.
68
71
  - `polendina --cleanup --webpack-config test/webpack.config.js test/test-*.js`
69
72
  where the config is `export default { externals: { standard: 'null' } }`.
70
73
  `js-ipld-schema` does this to stop webpack bundling a Node-only
@@ -149,8 +152,13 @@ Things that look wrong and are not. Each exists because something broke.
149
152
  `polendina/node_modules` directory.
150
153
  - **`import 'mocha/mocha.js'` for side effect, then `globalThis.mocha`.**
151
154
  Mocha 11 ships UMD, 12 ships ESM. The global is the common denominator.
152
- Both are supported at once. The `color()` / `useColors()` branch is the
153
- same idea for older mochas.
155
+ Both are supported at once.
156
+ - **`raw-loader` is a dependency that no polendina source references.**
157
+ Consumers load fixture files as text with
158
+ `import hex from '!!raw-loader!./fixtures/block.hex'` and declare nothing; the
159
+ loader resolves from polendina's tree through `resolveLoader.modules`. The
160
+ mocha fixtures assert it. Removing it or the second `resolveLoader` entry
161
+ breaks those consumers.
154
162
  - **`exprContextCritical: false`.** Mocha's bundle has a dynamic
155
163
  `require.resolve`; without this every run prints a webpack warning.
156
164
  - **`resolve.tsconfig: false`.** Webpack 5.109+ on Node 22.6+ follows a
@@ -160,9 +168,8 @@ Things that look wrong and are not. Each exists because something broke.
160
168
  forward-slashed.** Tests in subdirectories and Windows paths both broke the
161
169
  cwd-relative version. Three separate fixes landed here. Paths in
162
170
  `wrap-loader.cjs` are the most common regression site in the repo.
163
- - **`puppeteer.launch({ args: ['--no-sandbox'], headless: 'new' })`.**
164
- `--no-sandbox` for root in Docker and CI. `'new'` was a Puppeteer 20
165
- deprecation fix and is itself a candidate for removal on current Puppeteer.
171
+ - **`puppeteer.launch({ args: ['--no-sandbox'] })`.** `--no-sandbox` for
172
+ root in Docker and CI. Headless is Puppeteer's default and is not named.
166
173
  - **`st({ cache: false })`.** A stale cached bundle between page, worker and
167
174
  serviceworker runs on Windows.
168
175
  - **`setTimeout(...).unref()` for `--timeout`.** A clean finish must not be
@@ -316,11 +323,16 @@ Good work, roughly in order of value:
316
323
 
317
324
  - **Keep the matrix green.** Pick up a red dependabot PR, find the fallout,
318
325
  land the fix with the bump. This is the single most useful thing.
319
- - **Remove dead weight with evidence.** `raw-loader` is a dependency no source
320
- references. The `--coverage` flag and the `pti` TODO have never shipped. The
321
- commented-out `page.on('console')` block is dead. The `useColors` branch is
322
- unreachable on supported mochas. Each removal is one PR with a full test run
323
- and a `git log -S` showing where it came from.
326
+ - **Remove dead weight with evidence.** A flag that never shipped, a branch
327
+ unreachable on supported versions. Each removal is one PR with a full test
328
+ run and a `git log -S` showing where it came from. A dependency with no
329
+ reference in this repo is not evidence on its own: `raw-loader` is used
330
+ only from consumers' inline loader strings. Search the consumers first.
331
+ - **Make dynamic `import()` work in Worker and ServiceWorker modes.** Webpack
332
+ splits each one into a chunk and loads it through a script tag, which only
333
+ a page has. `module.parser.javascript.dynamicImportMode: 'eager'` would
334
+ inline them into the single bundle; the mocha fixtures' raw-loader case
335
+ would then pass as a dynamic import in all three scopes.
324
336
  - **Make failures legible.** When a resolve fails inside a consumer's
325
337
  dependency, the webpack error is the user's only clue. Better messages
326
338
  around bundle errors and the timeout are cheap wins.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [3.3.4](https://github.com/rvagg/polendina/compare/v3.3.3...v3.3.4) (2026-10-24)
2
+
3
+ ### Trivial Changes
4
+
5
+ * remove dead code, document and test raw-loader ([#206](https://github.com/rvagg/polendina/issues/206)) ([3ce94c0](https://github.com/rvagg/polendina/commit/3ce94c02208d72e8c174d6607bc30266e2ad69aa))
6
+
1
7
  ## [3.3.3](https://github.com/rvagg/polendina/compare/v3.3.2...v3.3.3) (2026-10-24)
2
8
 
3
9
  ### Tests
package/README.md CHANGED
@@ -20,6 +20,7 @@
20
20
  * [bare-sync](#bare-sync)
21
21
  * [bare-async](#bare-async)
22
22
  * [TypeScript support (and other Webpack extensions)](#typescript-support-and-other-webpack-extensions)
23
+ * [Loading fixture files](#loading-fixture-files)
23
24
  * [polendina-node: the minimal Node.js test runner](#polendina-node-the-minimal-nodejs-test-runner)
24
25
  * [Minimising Puppeteer's size](#minimising-puppeteers-size)
25
26
  * [Global and npx](#global-and-npx)
@@ -187,6 +188,16 @@ module.exports = {
187
188
  $ polendina --cleanup test/*.spec.ts --webpack-config webpack.config.ts.js
188
189
  ```
189
190
 
191
+ ## Loading fixture files
192
+
193
+ polendina bundles [raw-loader](https://ghub.io/raw-loader) so a test can read a fixture file as a string without any configuration, using webpack's inline loader syntax:
194
+
195
+ ```js
196
+ import hex from '!!raw-loader!./fixtures/block.hex'
197
+ ```
198
+
199
+ The leading `!!` stops any other loader from touching the file. In a CommonJS test the string is on `require('!!raw-loader!./fixtures/block.hex').default`. A dynamic `import()` works too, but only in page mode: webpack loads it as a separate chunk through a script tag, which Workers and ServiceWorkers do not have.
200
+
190
201
 
191
202
  ## `polendina-node`: the minimal Node.js test runner
192
203
 
package/lib/puppeteer.js CHANGED
@@ -1,15 +1,12 @@
1
1
  import puppeteer from 'puppeteer'
2
- // TODO
3
- // const pti = require('puppeteer-to-istanbul')
4
- // const { copyFile } = require('fs').promises
5
2
  import { log, error, logRaw, logWrite, flushLogs } from './log.js'
6
3
 
7
4
  // wrap our convoluted _run() function in a pure Promise that can handle
8
5
  // both standard throws and the callback that ends it. _run() needs to handle
9
6
  // ends in a few different ways, hence the hack.
10
- export function run (outputDir, port, timeout, mode, runner, coverage) {
7
+ export function run (outputDir, port, timeout, mode, runner) {
11
8
  return new Promise((resolve, reject) => {
12
- _run(outputDir, port, timeout, mode, runner, coverage, (err, errors) => {
9
+ _run(outputDir, port, timeout, mode, runner, (err, errors) => {
13
10
  if (err) {
14
11
  return reject(err)
15
12
  }
@@ -21,10 +18,10 @@ export function run (outputDir, port, timeout, mode, runner, coverage) {
21
18
  // this can throw, or it can end via `callback()` which may or may not contain
22
19
  // a runtime-error argument and a "number of errors from tests" argument.
23
20
  // the callback may be triggered by proper test end or failure, or a timeout.
24
- async function _run (outputDir, port, timeout, mode, runner, coverage, callback) {
21
+ async function _run (outputDir, port, timeout, mode, runner, callback) {
25
22
  let executionQueue = Promise.resolve()
26
23
 
27
- const browser = await puppeteer.launch({ args: ['--no-sandbox'], headless: 'new' })
24
+ const browser = await puppeteer.launch({ args: ['--no-sandbox'] })
28
25
  const [page] = await browser.pages()
29
26
 
30
27
  // this works as a debounce exit method, when process.stdout.write() is used
@@ -44,13 +41,6 @@ async function _run (outputDir, port, timeout, mode, runner, coverage, callback)
44
41
  executionQueue.then(async () => {
45
42
  flushLogs(true)
46
43
  executionQueue = null
47
- /* TODO
48
- if (coverage) {
49
- const jsCoverage = await page.coverage.stopJSCoverage()
50
- pti.write([...jsCoverage])
51
- await copyFile('build/bundle.js.map', '.nyc_output/js/bundle.js.map')
52
- }
53
- */
54
44
  await browser.close()
55
45
  }).catch(callback)
56
46
  .then(() => { callback(null, errors) })
@@ -64,24 +54,6 @@ async function _run (outputDir, port, timeout, mode, runner, coverage, callback)
64
54
  }
65
55
  }
66
56
 
67
- // this should be a rare, or impossible event since we intercept console.log
68
- /*
69
- page.on('console', (msg) => {
70
- if (!executionQueue) {
71
- error(`log after end: ${msg.text()}`)
72
- return
73
- }
74
- const args = []
75
- executionQueue = executionQueue.then(async () => {
76
- logRaw('info', -1, await Promise.all(args))
77
- })
78
- for (const arg of msg.args()) {
79
- args.push(arg.evaluate(n => n))
80
- }
81
- maybeEnd()
82
- })
83
- */
84
-
85
57
  page.on('error', (error) => console.error(error))
86
58
  page.on('pageerror', (error) => console.error(error))
87
59
 
@@ -97,10 +69,6 @@ async function _run (outputDir, port, timeout, mode, runner, coverage, callback)
97
69
  maybeEnd()
98
70
  })
99
71
 
100
- if (coverage) {
101
- await page.coverage.startJSCoverage()
102
- }
103
-
104
72
  const url = `http://localhost:${port}/?mode=${mode}&runner=${runner}`
105
73
  log(`Running ${runner} ${mode} tests with Puppeteer via\n ${url}`)
106
74
  if (runner !== 'mocha') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polendina",
3
- "version": "3.3.3",
3
+ "version": "3.3.4",
4
4
  "description": "Non-UI browser testing for JavaScript libraries from the command-line",
5
5
  "main": "polendina.js",
6
6
  "type": "module",
package/polendina-cli.js CHANGED
@@ -52,12 +52,6 @@ const argv = yargs(hideBin(process.argv))
52
52
  describe: 'Number of seconds to wait before auto-failing the test suite',
53
53
  default: 30
54
54
  })
55
- .option('coverage', {
56
- type: 'boolean',
57
- describe: 'Enable coverage reporting',
58
- default: false,
59
- hidden: true
60
- })
61
55
  .option('webpack-config', {
62
56
  type: 'string',
63
57
  describe: 'Supply a path to a webpack.config.js to merge into Polendina\'s Webpack config (use with caution)',
package/polendina.js CHANGED
@@ -132,7 +132,7 @@ export class Polendina {
132
132
  server.on('error', reject)
133
133
  server.listen(() => {
134
134
  const port = server.address().port
135
- puppeteer(this.outputDir, port, this._options.timeout, mode, this._options.runner, this._options.coverage)
135
+ puppeteer(this.outputDir, port, this._options.timeout, mode, this._options.runner)
136
136
  .then((errors) => {
137
137
  server.close(() => {
138
138
  if (this._options.runner !== 'mocha') {
@@ -26,12 +26,10 @@ export const log = {
26
26
  executionQueue(() => globalThis.polendinaLog(['info', logSeq++].concat(args)))
27
27
  return executionQueue
28
28
  },
29
- // TODO
30
29
  warn: (...args) => {
31
30
  executionQueue(() => globalThis.polendinaLog(['warn', logSeq++].concat(args)))
32
31
  return executionQueue
33
32
  },
34
- // TODO
35
33
  error: (...args) => {
36
34
  executionQueue(() => globalThis.polendinaLog(['error', logSeq++].concat(args)))
37
35
  return executionQueue
@@ -87,7 +85,6 @@ function setupLogging () {
87
85
  log.info.apply(null, args)
88
86
  }
89
87
 
90
- // TODO: differentiate
91
88
  console.warn = log.warn
92
89
  console.error = log.error
93
90
  }
@@ -8,12 +8,7 @@ import { registry, executionQueue, log, setup } from './common-run.js'
8
8
  async function runMocha () {
9
9
  const mochaLocal = globalThis.mocha
10
10
  mochaLocal.setup({ reporter: registry.argv.mochaReporter, ui: 'bdd' })
11
- // mocha@7 deprecated useColors()
12
- if (typeof mochaLocal.color === 'function') {
13
- mochaLocal.color(true)
14
- } else if (typeof mochaLocal.useColors === 'function') {
15
- mochaLocal.useColors(true)
16
- }
11
+ mochaLocal.color(true)
17
12
 
18
13
  // the well-behaved reporters, like spec, are easy to intercept
19
14
  mochaLocal.constructor.reporters.Base.consoleLog = log.info
@@ -0,0 +1 @@
1
+ raw fixture
@@ -11,4 +11,8 @@ describe('test suite 1', () => {
11
11
  it('test case 2', () => {
12
12
  assert.ok(true, 'all good')
13
13
  })
14
+
15
+ it('test case 3', () => {
16
+ assert.strictEqual(require('!!raw-loader!./fixture.txt').default, 'raw fixture') // eslint-disable-line import/no-webpack-loader-syntax
17
+ })
14
18
  })
@@ -0,0 +1 @@
1
+ raw fixture
@@ -1,6 +1,7 @@
1
1
  /* globals describe it */
2
2
 
3
3
  import fixture from './index.js'
4
+ import raw from '!!raw-loader!./fixture.txt' // eslint-disable-line import/no-webpack-loader-syntax
4
5
  import { assert } from 'chai'
5
6
 
6
7
  describe('test suite 1', () => {
@@ -11,4 +12,8 @@ describe('test suite 1', () => {
11
12
  it('test case 2', () => {
12
13
  assert.ok(true, 'all good')
13
14
  })
15
+
16
+ it('test case 3', () => {
17
+ assert.strictEqual(raw, 'raw fixture')
18
+ })
14
19
  })
@@ -25,7 +25,7 @@ testing bare fixture subdir
25
25
  it('should run in page', async () => {
26
26
  const { stdout, stderr, code } = await runCli('bare', bareAsyncFixture, ['--runner=bare-async'])
27
27
  if (code !== 0) {
28
- console.error(stderr)
28
+ console.error(stdout, stderr)
29
29
  }
30
30
  assert.strictEqual(code, 0, 'exited with zero exit code')
31
31
  const expected = expectedTemplate.replace(/WORKER/, 'not in worker')
@@ -39,7 +39,7 @@ testing bare fixture subdir
39
39
  it('should run in worker', async () => {
40
40
  const { stdout, stderr, code } = await runCli('bare', bareAsyncFixture, ['--runner=bare-async', '--worker', '--page=false'])
41
41
  if (code !== 0) {
42
- console.error(stderr)
42
+ console.error(stdout, stderr)
43
43
  }
44
44
  assert.strictEqual(code, 0, 'exited with zero exit code')
45
45
  const expected = expectedTemplate.replace(/WORKER/, 'in worker')
@@ -53,7 +53,7 @@ testing bare fixture subdir
53
53
  it('should run in serviceworker', async () => {
54
54
  const { stdout, stderr, code } = await runCli('bare', bareAsyncFixture, ['--runner=bare-async', '--serviceworker', '--page=false'])
55
55
  if (code !== 0) {
56
- console.error(stderr)
56
+ console.error(stdout, stderr)
57
57
  }
58
58
  assert.strictEqual(code, 0, 'exited with zero exit code')
59
59
  const expected = expectedTemplate.replace(/WORKER/, 'in serviceworker')
@@ -67,7 +67,7 @@ testing bare fixture subdir
67
67
  it('should run in page, worker and serviceworker', async () => {
68
68
  const { stdout, stderr, code } = await runCli('bare', bareAsyncFixture, ['--runner=bare-async', '--worker', '--serviceworker'])
69
69
  if (code !== 0) {
70
- console.error(stderr)
70
+ console.error(stdout, stderr)
71
71
  }
72
72
  assert.strictEqual(code, 0, 'exited with zero exit code')
73
73
 
@@ -21,7 +21,7 @@ testing bare fixture subdir
21
21
  it('should run in page', async () => {
22
22
  const { stdout, stderr, code } = await runCli('bare', bareSyncFixture, ['--runner=bare-sync'])
23
23
  if (code !== 0) {
24
- console.error(stderr)
24
+ console.error(stdout, stderr)
25
25
  }
26
26
  assert.strictEqual(code, 0, 'exited with zero exit code')
27
27
  const expected = expectedTemplate.replace(/WORKER/, 'not in worker')
@@ -35,7 +35,7 @@ testing bare fixture subdir
35
35
  it('should run in worker', async () => {
36
36
  const { stdout, stderr, code } = await runCli('bare', bareSyncFixture, ['--runner=bare-sync', '--worker', '--page=false'])
37
37
  if (code !== 0) {
38
- console.error(stderr)
38
+ console.error(stdout, stderr)
39
39
  }
40
40
  assert.strictEqual(code, 0, 'exited with zero exit code')
41
41
  const expected = expectedTemplate.replace(/WORKER/, 'in worker')
@@ -49,7 +49,7 @@ testing bare fixture subdir
49
49
  it('should run in serviceworker', async () => {
50
50
  const { stdout, stderr, code } = await runCli('bare', bareSyncFixture, ['--runner=bare-sync', '--serviceworker', '--page=false'])
51
51
  if (code !== 0) {
52
- console.error(stderr)
52
+ console.error(stdout, stderr)
53
53
  }
54
54
  assert.strictEqual(code, 0, 'exited with zero exit code')
55
55
  const expected = expectedTemplate.replace(/WORKER/, 'in serviceworker')
@@ -63,7 +63,7 @@ testing bare fixture subdir
63
63
  it('should run in page, worker and serviceworker', async () => {
64
64
  const { stdout, stderr, code } = await runCli('bare', bareSyncFixture, ['--runner=bare-sync', '--worker', '--serviceworker'])
65
65
  if (code !== 0) {
66
- console.error(stderr)
66
+ console.error(stdout, stderr)
67
67
  }
68
68
  assert.strictEqual(code, 0, 'exited with zero exit code')
69
69
 
@@ -21,6 +21,7 @@ for (const [consumerName, type] of Object.keys(ticks).flatMap((c) => ['cjs', 'es
21
21
  test suite 1
22
22
  ${ticks[consumerName]} test case 1
23
23
  ${ticks[consumerName]} test case 2
24
+ ${ticks[consumerName]} test case 3
24
25
 
25
26
  test suite 2 - worker
26
27
  ${ticks[consumerName]} is WORKER
@@ -33,7 +34,7 @@ for (const [consumerName, type] of Object.keys(ticks).flatMap((c) => ['cjs', 'es
33
34
  it('should run in page', async () => {
34
35
  const { stdout, stderr, code } = await runCli(consumerName, mochaFixture, [])
35
36
  if (code !== 0) {
36
- console.error(stderr)
37
+ console.error(stdout, stderr)
37
38
  }
38
39
  assert.strictEqual(code, 0, 'exited with zero exit code')
39
40
  const expected = expectedTemplate.replace(/WORKER/, 'not in worker')
@@ -47,7 +48,7 @@ for (const [consumerName, type] of Object.keys(ticks).flatMap((c) => ['cjs', 'es
47
48
  it('should run in worker', async () => {
48
49
  const { stdout, stderr, code } = await runCli(consumerName, mochaFixture, ['--worker', '--page=false'])
49
50
  if (code !== 0) {
50
- console.error(stderr)
51
+ console.error(stdout, stderr)
51
52
  }
52
53
  assert.strictEqual(code, 0, 'exited with zero exit code')
53
54
  const expected = expectedTemplate.replace(/WORKER/, 'in worker')
@@ -61,7 +62,7 @@ for (const [consumerName, type] of Object.keys(ticks).flatMap((c) => ['cjs', 'es
61
62
  it('should run in serviceworker', async () => {
62
63
  const { stdout, stderr, code } = await runCli(consumerName, mochaFixture, ['--serviceworker', '--page=false'])
63
64
  if (code !== 0) {
64
- console.error(stderr)
65
+ console.error(stdout, stderr)
65
66
  }
66
67
  assert.strictEqual(code, 0, 'exited with zero exit code')
67
68
  const expected = expectedTemplate.replace(/WORKER/, 'in serviceworker')
@@ -75,7 +76,7 @@ for (const [consumerName, type] of Object.keys(ticks).flatMap((c) => ['cjs', 'es
75
76
  it('should run in page, worker and serviceworker', async () => {
76
77
  const { stdout, stderr, code } = await runCli(consumerName, mochaFixture, ['--worker', '--serviceworker'])
77
78
  if (code !== 0) {
78
- console.error(stderr)
79
+ console.error(stdout, stderr)
79
80
  }
80
81
  assert.strictEqual(code, 0, 'exited with zero exit code')
81
82
 
@@ -25,7 +25,7 @@ Took X seconds
25
25
  `
26
26
  const { stdout, stderr, code } = await runNodeCli('bare', bareSyncFixture, 'bare-sync')
27
27
  if (code !== 0) {
28
- console.error(stderr)
28
+ console.error(stdout, stderr)
29
29
  }
30
30
  assert.strictEqual(code, 0, 'exited with zero exit code')
31
31
  assert.strictEqual(removeTiming(stdout), expected)
@@ -60,7 +60,7 @@ Took X seconds
60
60
  `
61
61
  const { stdout, stderr, code } = await runNodeCli('bare', bareAsyncFixture, 'bare-async')
62
62
  if (code !== 0) {
63
- console.error(stderr)
63
+ console.error(stdout, stderr)
64
64
  }
65
65
  assert.strictEqual(code, 0, 'exited with zero exit code')
66
66
  assert.strictEqual(removeTiming(stdout), expected)
package/test/test-tape.js CHANGED
@@ -35,7 +35,7 @@ ok 5 all good
35
35
  it('should run in page', async () => {
36
36
  const { stdout, stderr, code } = await runCli('tape', tapeFixture, ['--runner=tape'])
37
37
  if (code !== 0) {
38
- console.error(stderr)
38
+ console.error(stdout, stderr)
39
39
  }
40
40
  assert.strictEqual(code, 0, 'exited with zero exit code')
41
41
  const expected = expectedTemplate.replace(/WORKER/, 'not in worker')
@@ -49,7 +49,7 @@ ok 5 all good
49
49
  it('should run in worker', async () => {
50
50
  const { stdout, stderr, code } = await runCli('tape', tapeFixture, ['--runner=tape', '--worker', '--page=false'])
51
51
  if (code !== 0) {
52
- console.error(stderr)
52
+ console.error(stdout, stderr)
53
53
  }
54
54
  assert.strictEqual(code, 0, 'exited with zero exit code')
55
55
  const expected = expectedTemplate.replace(/WORKER/, 'in worker')
@@ -63,7 +63,7 @@ ok 5 all good
63
63
  it('should run in serviceworker', async () => {
64
64
  const { stdout, stderr, code } = await runCli('tape', tapeFixture, ['--runner=tape', '--serviceworker', '--page=false'])
65
65
  if (code !== 0) {
66
- console.error(stderr)
66
+ console.error(stdout, stderr)
67
67
  }
68
68
  assert.strictEqual(code, 0, 'exited with zero exit code')
69
69
  const expected = expectedTemplate.replace(/WORKER/, 'in serviceworker')
@@ -77,7 +77,7 @@ ok 5 all good
77
77
  it('should run in page, worker and serviceworker', async () => {
78
78
  const { stdout, stderr, code } = await runCli('tape', tapeFixture, ['--runner=tape', '--worker', '--serviceworker'])
79
79
  if (code !== 0) {
80
- console.error(stderr)
80
+ console.error(stdout, stderr)
81
81
  }
82
82
  assert.strictEqual(code, 0, 'exited with zero exit code')
83
83
 
@@ -14,7 +14,7 @@ WOOP is set
14
14
  `
15
15
  const { stdout, stderr, code } = await runCli('bare', webpackMergeFixture, ['--runner=bare-sync', '--webpack-config', 'webpack.config.js'])
16
16
  if (code !== 0) {
17
- console.error(stderr)
17
+ console.error(stdout, stderr)
18
18
  }
19
19
  assert.strictEqual(code, 0, 'exited with zero exit code')
20
20
  if (!stdout.includes(expected)) {