polendina 3.3.1 → 3.3.3

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.
Files changed (35) hide show
  1. package/.github/dependabot.yml +1 -130
  2. package/.github/workflows/test-and-release.yml +1 -3
  3. package/AGENTS.md +400 -0
  4. package/CHANGELOG.md +16 -0
  5. package/CLAUDE.md +1 -0
  6. package/lib/webpack.config.js +5 -2
  7. package/package.json +3 -4
  8. package/polendina.js +7 -3
  9. package/test/common.js +27 -10
  10. package/test/consumer/package.json +11 -0
  11. package/test/fixtures/bare-async/package.json +1 -14
  12. package/test/fixtures/bare-async-esm/package.json +2 -15
  13. package/test/fixtures/bare-async-failure/package.json +1 -14
  14. package/test/fixtures/bare-async-failure-esm/package.json +2 -15
  15. package/test/fixtures/bare-sync/package.json +1 -14
  16. package/test/fixtures/bare-sync-esm/package.json +2 -15
  17. package/test/fixtures/bare-sync-failure/package.json +1 -14
  18. package/test/fixtures/bare-sync-failure-esm/package.json +2 -15
  19. package/test/fixtures/mocha/package.json +1 -15
  20. package/test/fixtures/mocha-esm/package.json +2 -16
  21. package/test/fixtures/mocha-failure/package.json +1 -15
  22. package/test/fixtures/mocha-failure-esm/package.json +2 -16
  23. package/test/fixtures/tape/package.json +1 -14
  24. package/test/fixtures/tape-esm/package.json +2 -15
  25. package/test/fixtures/tape-failure/package.json +1 -14
  26. package/test/fixtures/tape-failure-esm/package.json +2 -15
  27. package/test/fixtures/webpack-merge/package.json +1 -14
  28. package/test/setup.js +96 -0
  29. package/test/test-bare-async.js +10 -14
  30. package/test/test-bare-sync.js +10 -14
  31. package/test/test-mocha.js +23 -23
  32. package/test/test-packed-install.js +47 -0
  33. package/test/test-polendina-node.js +9 -19
  34. package/test/test-tape.js +10 -14
  35. package/test/test-webpack-merge.js +3 -7
@@ -17,138 +17,9 @@ updates:
17
17
  include: 'scope'
18
18
 
19
19
  - package-ecosystem: 'npm'
20
- directory: '/test/fixtures/bare-async'
20
+ directory: '/test/consumer'
21
21
  schedule:
22
22
  interval: 'daily'
23
23
  commit-message:
24
24
  prefix: 'chore'
25
25
  include: 'scope'
26
-
27
- - package-ecosystem: 'npm'
28
- directory: '/test/fixtures/bare-async-esm'
29
- schedule:
30
- interval: 'daily'
31
- commit-message:
32
- prefix: 'chore'
33
- include: 'scope'
34
-
35
- - package-ecosystem: 'npm'
36
- directory: '/test/fixtures/bare-async-failure'
37
- schedule:
38
- interval: 'daily'
39
- commit-message:
40
- prefix: 'chore'
41
- include: 'scope'
42
-
43
- - package-ecosystem: 'npm'
44
- directory: '/test/fixtures/bare-async-failure-esm'
45
- schedule:
46
- interval: 'daily'
47
- commit-message:
48
- prefix: 'chore'
49
- include: 'scope'
50
-
51
- - package-ecosystem: 'npm'
52
- directory: '/test/fixtures/bare-sync'
53
- schedule:
54
- interval: 'daily'
55
- commit-message:
56
- prefix: 'chore'
57
- include: 'scope'
58
-
59
- - package-ecosystem: 'npm'
60
- directory: '/test/fixtures/bare-sync-esm'
61
- schedule:
62
- interval: 'daily'
63
- commit-message:
64
- prefix: 'chore'
65
- include: 'scope'
66
-
67
- - package-ecosystem: 'npm'
68
- directory: '/test/fixtures/bare-sync-failure'
69
- schedule:
70
- interval: 'daily'
71
- commit-message:
72
- prefix: 'chore'
73
- include: 'scope'
74
-
75
- - package-ecosystem: 'npm'
76
- directory: '/test/fixtures/bare-sync-failure-esm'
77
- schedule:
78
- interval: 'daily'
79
- commit-message:
80
- prefix: 'chore'
81
- include: 'scope'
82
-
83
- - package-ecosystem: 'npm'
84
- directory: '/test/fixtures/mocha'
85
- schedule:
86
- interval: 'daily'
87
- commit-message:
88
- prefix: 'chore'
89
- include: 'scope'
90
-
91
- - package-ecosystem: 'npm'
92
- directory: '/test/fixtures/mocha-esm'
93
- schedule:
94
- interval: 'daily'
95
- commit-message:
96
- prefix: 'chore'
97
- include: 'scope'
98
-
99
- - package-ecosystem: 'npm'
100
- directory: '/test/fixtures/mocha-failure'
101
- schedule:
102
- interval: 'daily'
103
- commit-message:
104
- prefix: 'chore'
105
- include: 'scope'
106
-
107
- - package-ecosystem: 'npm'
108
- directory: '/test/fixtures/mocha-failure-esm'
109
- schedule:
110
- interval: 'daily'
111
- commit-message:
112
- prefix: 'chore'
113
- include: 'scope'
114
-
115
- - package-ecosystem: 'npm'
116
- directory: '/test/fixtures/tape'
117
- schedule:
118
- interval: 'daily'
119
- commit-message:
120
- prefix: 'chore'
121
- include: 'scope'
122
-
123
- - package-ecosystem: 'npm'
124
- directory: '/test/fixtures/tape-esm'
125
- schedule:
126
- interval: 'daily'
127
- commit-message:
128
- prefix: 'chore'
129
- include: 'scope'
130
-
131
- - package-ecosystem: 'npm'
132
- directory: '/test/fixtures/tape-failure'
133
- schedule:
134
- interval: 'daily'
135
- commit-message:
136
- prefix: 'chore'
137
- include: 'scope'
138
-
139
- - package-ecosystem: 'npm'
140
- directory: '/test/fixtures/tape-failure-esm'
141
- schedule:
142
- interval: 'daily'
143
- commit-message:
144
- prefix: 'chore'
145
- include: 'scope'
146
-
147
- - package-ecosystem: 'npm'
148
- directory: '/test/fixtures/webpack-merge'
149
- schedule:
150
- interval: 'daily'
151
- commit-message:
152
- prefix: 'chore'
153
- include: 'scope'
154
-
@@ -19,9 +19,7 @@ jobs:
19
19
  run: |
20
20
  npm install --no-progress
21
21
  - name: Run tests
22
- run: |
23
- npm config set script-shell bash
24
- npm test
22
+ run: npm test
25
23
  release:
26
24
  name: Release
27
25
  needs: test
package/AGENTS.md ADDED
@@ -0,0 +1,400 @@
1
+ # polendina agent guide
2
+
3
+ polendina runs a Node.js-style test suite in headless Chrome from the command
4
+ line. You give it the same globs you give mocha, it bundles them with webpack,
5
+ serves them to Puppeteer and prints the result as if the tests ran natively.
6
+ `npm test` can run Node.js and browser tests back to back with no browser
7
+ config, no karma, no selenium.
8
+
9
+ This file is for coding agents and the people driving them. `README.md` is the
10
+ user manual. This file is the contract for changing the code.
11
+
12
+ ## Telos
13
+
14
+ One sentence: **simple browser testing for Node.js-style packages, so
15
+ `npm test` just works.**
16
+
17
+ Everything else follows from it. Judge every change against these, in order:
18
+
19
+ 1. **Zero config by default, overridable when needed.** A dependent project
20
+ adds one script line, `polendina --cleanup test/test-*.js`, and nothing
21
+ else. No config file, no browser install, no build step. When a project
22
+ needs more, flags cover the common cases and `--webpack-config` opens the
23
+ bundler for the rest. A change that makes the default path need more is
24
+ going the wrong way. A change that closes off an override is too.
25
+ 2. **The project's own test framework and dependencies win.** polendina does
26
+ not depend on mocha or tape. It bundles whatever the consumer has installed.
27
+ polendina's `node_modules` is a fallback for its own shims only.
28
+ 3. **Faithful terminal output.** Browser output must look like native mocha or
29
+ tape output, in order, with the same exit code. The test suite asserts this
30
+ by string equality on stdout. Output ordering bugs are real bugs.
31
+ 4. **Page, Worker and ServiceWorker are peers.** Every runner works in all
32
+ three, selected by flag. Every fixture asserts which scope it is in.
33
+ 5. **Library testing, not UI testing.** No screenshots, no DOM assertions, no
34
+ coverage. The browser is a runtime, not a subject.
35
+ 6. **Ride the ecosystem's current majors.** Caret ranges, dependabot, release
36
+ on every bump. The cost of this is the bitrot section below. The benefit is
37
+ that consumers get current Chrome and current webpack without thinking.
38
+
39
+ Radical change is allowed when it serves the telos better than the current
40
+ model. Replacing webpack is not off the table. Adding a config file is.
41
+
42
+ ## Who depends on polendina
43
+
44
+ Small to mid-sized libraries that target Node.js and browsers and want a cheap
45
+ browser run in CI: `cborg`, `through2`, `ripemd160`, the IPLD JavaScript
46
+ packages and others. `cborg` is the reference consumer and the first thing to
47
+ try a change against. Do it with a packed tarball, not a symlink; see
48
+ "Install topologies" below for why:
49
+
50
+ ```bash
51
+ npm pack # in polendina
52
+ npm install --no-save ../polendina/polendina-*.tgz # in the consumer
53
+ npm run test:browser
54
+ ```
55
+
56
+ Real usage, in increasing order of involvement. Each shape must keep working:
57
+
58
+ - `polendina --cleanup test/test-*.js`. The default. Mocha, page only.
59
+ - `polendina --page --worker --serviceworker --cleanup test/*-test.js`. All
60
+ three scopes, common for data-structure libraries.
61
+ - `polendina --timeout 320 --runner=mocha --cleanup test/browser.js`. Heavy
62
+ suites with a dedicated browser entry file and a long timeout.
63
+ - `polendina --cleanup dist/cjs/node-test/test-*.js`. Tests run from a build
64
+ output directory rather than source.
65
+ - `polendina --runner=bare-async test/test.js` paired with
66
+ `polendina-node bare-async test/test.js`. The same files run in Node.js and
67
+ the browser with no test framework at all.
68
+ - `polendina --cleanup --webpack-config test/webpack.config.js test/test-*.js`
69
+ where the config is `export default { externals: { standard: 'null' } }`.
70
+ `js-ipld-schema` does this to stop webpack bundling a Node-only
71
+ dependency. The merged config is the escape hatch for anything polendina
72
+ does not do itself: externals, loaders for TypeScript, `DefinePlugin`.
73
+
74
+ Consumers call the CLI only. `polendina.js` exports a `Polendina` class but
75
+ the CLI is the API. Flags, defaults, exit codes, the shape of stdout and the
76
+ fact that `--webpack-config` is merged into a plain webpack config are the
77
+ compatibility surface. Active consumers track releases through dependabot,
78
+ so a breaking change is acceptable when it is worth the bump; it should be
79
+ marked as one and say what the consumer must do.
80
+
81
+ ## How it works
82
+
83
+ ```text
84
+ CLI (yargs) -> Polendina.build() webpack: test files + <runner>-run.js -> build/bundle.js
85
+ -> Polendina.run() for each mode: st serves build/, Puppeteer loads index.html
86
+ page-run.js picks page | Worker | ServiceWorker, loads bundle.js
87
+ <runner>-run.js drains the test registry, runs, reports
88
+ exposeFunction channel -> lib/log.js -> stdout
89
+ -> cleanup() rimraf build/
90
+ ```
91
+
92
+ Files:
93
+
94
+ - `polendina-cli.js` flags and the top-level flow. `polendina.js` build,
95
+ serve, run, cleanup. `polendina-node-cli.js` the Node.js runner for
96
+ `bare-sync` and `bare-async` files, sharing `resources/bare.js`.
97
+ - `lib/webpack.config.js` the one webpack config. `lib/wrap-loader.cjs` the
98
+ pitch loader that defers test execution. `lib/puppeteer.js` launches
99
+ Chrome, wires the output channel, owns the timeout. `lib/log.js` sequenced
100
+ stdout.
101
+ - `resources/` is copied verbatim into the output dir at build time.
102
+ `index.html` and `page-run.js` are served static. `common-run.js`,
103
+ `test-registry.cjs` and one of `mocha-run.js`, `tape-run.js`, `bare-run.js`
104
+ are bundled as the last entry.
105
+ - `test/` packs polendina, installs it into temporary consumers outside the
106
+ repo, one per runner, and runs the CLI against `test/fixtures/*` from
107
+ inside them, one fixture per runner, in CJS and ESM, passing and failing.
108
+
109
+ ### The deferral trick
110
+
111
+ Test files must not run when the bundle loads. Mocha needs `describe()` calls
112
+ after `mocha.setup()`; tape needs its harness stream attached first.
113
+ `wrap-loader.cjs` replaces each test module with a stub that pushes
114
+ `{ name, load }` onto `test-registry.cjs`, where `load()` is a sync
115
+ `require('!!./file.js')`. The `*-run.js` entry, appended last, drains the
116
+ registry when ready. This is why the loader emits CJS, why the rule has
117
+ `type: 'javascript/auto'`, and why the registry and loader are `.cjs` in a
118
+ `"type": "module"` package.
119
+
120
+ ### Output channel
121
+
122
+ Browser `console.log` is overridden to call `polendinaLog(['info', seq, ...])`
123
+ through `page.exposeFunction`. Workers and ServiceWorkers forward through
124
+ `postMessage` to the page, which relays. Mocha's `BrowserStdout` polyfill of
125
+ `process.stdout.write` is detected by stack regex and routed to
126
+ `polendinaWrite` so partial lines stay partial. `lib/log.js` reorders by
127
+ sequence number because exposed-function calls can arrive out of order.
128
+ `end()` is debounced 100ms because `polendinaEnd` can beat trailing writes.
129
+ Touch any of this and run the full matrix on Windows and macOS CI before
130
+ believing it.
131
+
132
+ ## Fences
133
+
134
+ Things that look wrong and are not. Each exists because something broke.
135
+
136
+ - **`resolve.modules` and `resolveLoader.modules` list the relative
137
+ `node_modules` first, polendina's own `node_modules` second.** The relative
138
+ entry walks up from the requesting file as Node does, so the consumer's
139
+ mocha, tape and chai win and hoisted or nested dependencies resolve from a
140
+ workspace package or any subdirectory. An absolute `cwd/node_modules` here
141
+ does not walk and breaks both. polendina has no mocha or tape in its own
142
+ tree, on purpose.
143
+ - **Tape gets `resolve.fallback` for `stream` and `path`, `fs: false`, and
144
+ `ProvidePlugin({ process })`.** Webpack 5 dropped Node core polyfills.
145
+ Mocha's browser bundle is self-contained; tape's is not. `process`,
146
+ `path-browserify`, `stream-browserify` and `readable-stream` are
147
+ dependencies solely for this. They are resolved with `createRequire` from
148
+ `polendina.js`, never by path, because a hoisted consumer install has no
149
+ `polendina/node_modules` directory.
150
+ - **`import 'mocha/mocha.js'` for side effect, then `globalThis.mocha`.**
151
+ 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.
154
+ - **`exprContextCritical: false`.** Mocha's bundle has a dynamic
155
+ `require.resolve`; without this every run prints a webpack warning.
156
+ - **`resolve.tsconfig: false`.** Webpack 5.109+ on Node 22.6+ follows a
157
+ dependency's `tsconfig.json`, which may `extends` a package that was never
158
+ published. Every resolve inside that dependency then fails.
159
+ - **Registry path in the loader is relative to the test file's directory,
160
+ forward-slashed.** Tests in subdirectories and Windows paths both broke the
161
+ cwd-relative version. Three separate fixes landed here. Paths in
162
+ `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.
166
+ - **`st({ cache: false })`.** A stale cached bundle between page, worker and
167
+ serviceworker runs on Windows.
168
+ - **`setTimeout(...).unref()` for `--timeout`.** A clean finish must not be
169
+ held open; on fire it closes the browser and rejects.
170
+ - **`polendina-node` unwraps `mod['module.exports']`.** Node 23+ exposes CJS
171
+ loaded via `import()` under that named export; `bare-async` export
172
+ enumeration would otherwise see the wrong keys.
173
+ - **`polendina-cli` exits with the mocha failure count; `polendina-node` exits
174
+ 0 or 1.** The first matches mocha's own behaviour. Keep both.
175
+ - **The test frameworks live in `test/consumer/package.json`, not the root
176
+ package, and `test:run` uses `npx --yes mocha`.** mocha and tape must never
177
+ become devDependencies of the root package: they would land in polendina's
178
+ `node_modules` and silently mask a broken consumer resolve. That file is
179
+ the one place dependabot bumps them; it is never installed as is. The flag,
180
+ not an `npm_config_yes=` prefix, because npm scripts run under cmd.exe on
181
+ Windows and a `VAR=value` prefix is not a command there.
182
+ - **One consumer per runner, never one consumer with every framework.** Tape
183
+ and polendina both depend on `readable-stream`. Installed together with
184
+ mocha, npm nests whichever versions clash and the fixtures run on a tree no
185
+ real consumer has. Each consumer installs only what its runner's fixtures
186
+ import.
187
+ - **Fixture `package.json` files declare only a name and, for ESM, a
188
+ `type`.** Their dependencies come from the consumer they are copied into.
189
+ The webpack-merge fixture's config `require('webpack')` finds the hoisted
190
+ copy there, as a real consumer's would.
191
+ - **`test/common.js` asserts empty stderr on success and always passes
192
+ `--cleanup`.** Any stray warning is a test failure. Fixtures stay clean
193
+ between runs only because of the flag.
194
+ - **`chore` commits cut a patch release.** Every dependency bump ships. This is
195
+ deliberate: consumers get current Chrome and webpack without a human in the
196
+ loop.
197
+
198
+ ## Bitrot
199
+
200
+ What breaks, where it breaks, how it gets fixed. Expect each of these again.
201
+
202
+ | Trigger | Where it lands | Pattern |
203
+ |---------|----------------|---------|
204
+ | Mocha major | `resources/mocha-run.js`, fixture `package.json` | Import path, export shape or API rename in the browser bundle. Support the new and previous major together. |
205
+ | Webpack minor or major | `lib/webpack.config.js`, `polendina.js` | A resolution default changes or a polyfill vanishes. Fix with a config key, not a loader. |
206
+ | Puppeteer major | `lib/puppeteer.js` | Launch option deprecations. Usually a one-line change or nothing. |
207
+ | Node.js current | `polendina-node-cli.js`, anything ESM | Interop changes surface first on the `current` CI leg. |
208
+ | Any dependency going ESM-only | import sites | Named imports (`{ glob }`, `{ rimraf }`, `{ merge }`, `hideBin`). |
209
+ | Output ordering | `lib/log.js`, `resources/common-run.js`, `lib/puppeteer.js` | Shows up only on Windows and macOS CI as interleaved output. Reproduce there before changing anything. |
210
+ | Path separators | `lib/wrap-loader.cjs` | Windows. Normalise to forward slashes at the point of string generation. |
211
+
212
+ The CI matrix is `lts/*` and `current` on Linux, macOS and Windows. It is
213
+ there to catch these early, and it does. A red `current` leg on an otherwise
214
+ green matrix is a Node.js interop issue, not a flaky test.
215
+
216
+ ## Supported environment
217
+
218
+ - Node.js: Active and Maintenance lines on the official schedule. Dropping an
219
+ end-of-life line is a `chore`.
220
+ - Chrome: whatever the current Puppeteer ships. No other browser. Adding one
221
+ would be a new project.
222
+ - Test runners: mocha (default), tape, `bare-sync`, `bare-async`. Current and
223
+ previous major of mocha and tape at once, as the fixtures pin them.
224
+ - Consumer module format: CJS and ESM, both covered by fixtures.
225
+ - Platform: Linux, macOS, Windows. Windows is a first-class CI leg.
226
+
227
+ ## Testing polendina itself
228
+
229
+ ```bash
230
+ npm test # lint and the whole matrix
231
+ npx mocha --require test/setup.js test/test-mocha.js # one runner's suite
232
+ POLENDINA_KEEP_CONSUMERS=1 npx mocha --require test/setup.js test/test-tape.js # keep the consumers to poke at
233
+ cd test/fixtures/mocha && ../../../polendina-cli.js test-*.js --stats # by hand, in-repo layout, keep build/
234
+ ```
235
+
236
+ `test/setup.js` is a mocha global fixture. It packs the working tree, writes
237
+ a `package.json` per consumer, installs the tarball with that consumer's
238
+ frameworks into a temporary directory, copies `test/fixtures` in, and removes
239
+ everything afterwards. Its `variants` table maps a consumer name to the
240
+ packages it installs: `mocha` and `bare` take chai, `tape` takes tape,
241
+ `mocha11` pins the previous mocha major. Bare names take their version range
242
+ from `test/consumer/package.json`. Each suite names the consumer it runs in
243
+ when it calls `runCli`. The installs run in parallel from the npm cache with
244
+ the Chrome that puppeteer already downloaded for the root install, so they
245
+ need a warm cache and take well under a minute.
246
+
247
+ ### Install topologies
248
+
249
+ polendina resolves modules two ways at once: Node resolution for its own
250
+ imports, and webpack `resolve.modules` for the bundle, which checks the
251
+ consumer's `node_modules` then polendina's. Where "polendina's
252
+ `node_modules`" actually is depends on how it was installed, and no single
253
+ test setup covers all three cases.
254
+
255
+ | Topology | polendina's own `node_modules` | What it hides |
256
+ |----------|-------------------------------|---------------|
257
+ | Fixtures run in this repo by hand | The dev tree, two directories up | Fixtures can walk up and find polendina's dev dependencies. A missing dependency, or any package added to the root, is silently masked. Fine for poking at a bundle, proves nothing about resolution. |
258
+ | Symlinked working copy in a consumer | polendina's own full tree, separate from the consumer's | Two complete trees, no hoisting, no version contention. Cleaner than any real install. Will not reproduce a hoisting break. |
259
+ | Published install in a consumer | Mostly absent: dependencies are hoisted into the consumer's flat tree | Anything that assumes a file exists under `polendina/node_modules/` breaks. Version conflicts between polendina's dependencies and the consumer's resolve by npm nesting and are then found by `resolve.modules` in whichever order npm left them. This is reality, and it is what the suite runs in. |
260
+
261
+ Consequences:
262
+
263
+ - Never reference polendina's own `node_modules` by path. Resolve through
264
+ Node from a file inside polendina so it works wherever npm put the package.
265
+ - The fixtures sit in a subdirectory of the consumer, so the suite also
266
+ stands in for a workspace package whose dependencies are hoisted to a
267
+ parent. Module resolution must walk up.
268
+ - `test/test-packed-install.js` asserts the layout itself: polendina's
269
+ dependencies hoisted, each framework present only in its own consumer,
270
+ mocha and tape absent from polendina's own tree.
271
+
272
+ Rules the suite relies on:
273
+
274
+ - polendina is tested the way it is used: as a CLI installed from a tarball
275
+ and run from inside a consumer directory. Do not import `polendina.js` into
276
+ tests to make them faster.
277
+ - The mocha spec reporter's pass symbol differs between majors in the
278
+ browser bundle, so mocha expectations take the tick from a per-consumer
279
+ table rather than a literal.
280
+ - Every runner has a passing and a failing fixture, in CJS and ESM. A new
281
+ runner or mode adds all four, a row in `variants` with its framework, and
282
+ that framework in `test/consumer/package.json`. A new code path in an
283
+ existing runner adds an assertion to the existing fixtures rather than a
284
+ new fixture.
285
+ - Expected output is matched as exact multi-line strings with timings
286
+ scrubbed. When output changes on purpose, update the expectation and say so
287
+ in the PR.
288
+ - Use `--stats` and skip `--cleanup` to inspect `build/bundle.js` and
289
+ `webpack-stats.json` when a resolve goes wrong.
290
+
291
+ ## Making a change
292
+
293
+ 1. Reproduce in a fixture first. If no fixture shows the problem, add the
294
+ smallest assertion to an existing one that does.
295
+ 2. Run that runner's suite, then the whole matrix. Windows and macOS only run
296
+ in CI, so push a branch and watch all six legs before calling it done.
297
+ 3. Try it against `cborg` or another real consumer, installed from
298
+ `npm pack`, not a symlink. For anything touching resolution, also run a
299
+ tape fixture through the packed install, since no consumer in the wild
300
+ exercises that path.
301
+ 4. For anything touching `lib/webpack.config.js`, `lib/wrap-loader.cjs` or
302
+ the output channel, state in the PR which fence you touched and why it
303
+ still holds.
304
+ 5. New flag or behaviour: README options list, CLI `describe`, fixture
305
+ coverage, in that order. Defaults never change in a minor.
306
+ 6. One concern per PR. No drive-by tidying.
307
+
308
+ ## Direction
309
+
310
+ For an agent asked to improve this project without a brief. The project is
311
+ healthy but high-entropy: many moving parts, each on its own release
312
+ schedule. Impact comes from reducing the number of things that can rot
313
+ without reducing what works.
314
+
315
+ Good work, roughly in order of value:
316
+
317
+ - **Keep the matrix green.** Pick up a red dependabot PR, find the fallout,
318
+ 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.
324
+ - **Make failures legible.** When a resolve fails inside a consumer's
325
+ dependency, the webpack error is the user's only clue. Better messages
326
+ around bundle errors and the timeout are cheap wins.
327
+ - **Reduce the webpack surface.** Fewer config keys, fewer polyfills, fewer
328
+ reasons for a webpack release to break a run. If the tape polyfill set can
329
+ shrink, shrink it. Node core modules like `assert` are not polyfilled at
330
+ all, and that is deliberate; fixtures use chai.
331
+ - **Evaluate replacing webpack.** esbuild or rolldown could bundle, with a
332
+ plugin doing the wrap-loader's job. This is a major: `--webpack-config` is a
333
+ documented extension point used in the wild for externals and TypeScript.
334
+ Any replacement needs an equivalent escape hatch. It is telos-compatible
335
+ and would remove the biggest source of bitrot. Write up the trade before
336
+ writing code, and keep the CLI, output and fixtures identical.
337
+
338
+ Work to avoid:
339
+
340
+ - Any change that makes the consumer add a file or a flag to get what they
341
+ get today, or that removes an override they rely on.
342
+ - Adding a second browser, UI testing, screenshots or coverage.
343
+ - Pinning dependencies to dodge a break. Fix the break.
344
+ - Adding mocha or tape to the root package in any form.
345
+ - Rewriting the output channel for elegance. It is shaped by CI on three
346
+ platforms and the shape is the fix.
347
+ - Touching the release config, dependabot config or CI matrix unless that is
348
+ the point of the PR.
349
+
350
+ ## Code style
351
+
352
+ - StandardJS: no semicolons, single quotes, space before function parens.
353
+ Fix lint, never disable it.
354
+ - Plain JavaScript, ESM in the package, `.cjs` only where webpack demands it.
355
+ No TypeScript, no build step.
356
+ - Comments describe the code as it is now. Never what changed, why a line was
357
+ added or which issue it fixed; that belongs in the commit. Every comment
358
+ earns its place and its word count.
359
+ - Simple language in prose, docs and messages. Short sentences. Plain
360
+ punctuation: no em or en dashes, no emoji outside the existing log brand.
361
+ - Nothing new at the repository root. Node-side code in `lib/`, browser-side
362
+ in `resources/`, tests and fixtures in `test/`.
363
+ - A new runtime dependency needs a sentence in the PR saying why the consumer
364
+ should carry it.
365
+
366
+ ## Commits and pull requests
367
+
368
+ Conventional commits drive semantic-release on every push to `master`.
369
+
370
+ | Type | Release |
371
+ |------|---------|
372
+ | `feat` | minor |
373
+ | `fix`, `perf`, `chore`, `docs`, `test`, `revert` | patch |
374
+ | `refactor`, `ci`, `build`, `style` | none |
375
+ | any type with `!` and a `BREAKING CHANGE:` footer | major |
376
+ | scope `no-release` | none |
377
+
378
+ - Subject: imperative, lower case, no trailing period, under 72 characters.
379
+ Scopes in use: `mocha`, `deps`, `ci`.
380
+ - Terse and undecorated. No labels, badges or boilerplate.
381
+ - A breaking change says who is affected and what they must do. Dropping a
382
+ mocha or Node.js major is breaking even when the blast radius is small.
383
+ - PRs are squash-merged and the title becomes the commit subject. History is
384
+ linear.
385
+ - `CHANGELOG.md` and the version in `package.json` are generated. Never edit
386
+ them.
387
+
388
+ ## Keep this file true
389
+
390
+ When a change makes anything here inaccurate (a file moved, a flag changed, a
391
+ fence removed, a bitrot pattern resolved for good), update this file in the
392
+ same PR. Describe the project as it is. No history, no change notes.
393
+
394
+ ## References
395
+
396
+ - Puppeteer: https://pptr.dev/
397
+ - Webpack resolve options: https://webpack.js.org/configuration/resolve/
398
+ - Webpack loader pitching: https://webpack.js.org/api/loaders/#pitching-loader
399
+ - Mocha browser usage: https://mochajs.org/#running-mocha-in-the-browser
400
+ - Reference consumer, `cborg`: https://github.com/rvagg/cborg
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [3.3.3](https://github.com/rvagg/polendina/compare/v3.3.2...v3.3.3) (2026-10-24)
2
+
3
+ ### Tests
4
+
5
+ * run fixtures from a packed install in temporary consumers ([#205](https://github.com/rvagg/polendina/issues/205)) ([d1601fc](https://github.com/rvagg/polendina/commit/d1601fcc94b50bb6192629173500b85443a405f2))
6
+
7
+ ## [3.3.2](https://github.com/rvagg/polendina/compare/v3.3.1...v3.3.2) (2026-10-24)
8
+
9
+ ### Bug Fixes
10
+
11
+ * **tape:** resolve browser polyfills via createRequire, not by path ([#203](https://github.com/rvagg/polendina/issues/203)) ([f1441cf](https://github.com/rvagg/polendina/commit/f1441cfd77b2f4dc76f8f302abf0143f8e60d7fa))
12
+
13
+ ### Trivial Changes
14
+
15
+ * add agent context files ([#204](https://github.com/rvagg/polendina/issues/204)) ([e822ab3](https://github.com/rvagg/polendina/commit/e822ab366f6b9a6ade72ab2e93ca08952b23e56a))
16
+
1
17
  ## [3.3.1](https://github.com/rvagg/polendina/compare/v3.3.0...v3.3.1) (2026-10-19)
2
18
 
3
19
  ### Trivial Changes
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -34,14 +34,17 @@ export function webpackConfig (env, options, runner) {
34
34
  // published deps; tsconfig-aware resolution follows it and fails every resolve
35
35
  // inside that dependency.
36
36
  tsconfig: false,
37
+ // the consumer's tree first, walking up from the requesting file as Node
38
+ // does so hoisted and nested dependencies resolve; polendina's own tree
39
+ // is only a fallback for its runner shims
37
40
  modules: [
38
- path.join(process.cwd(), 'node_modules'),
41
+ 'node_modules',
39
42
  path.join(__dirname, '../node_modules')
40
43
  ]
41
44
  },
42
45
  resolveLoader: {
43
46
  modules: [
44
- path.join(process.cwd(), 'node_modules'),
47
+ 'node_modules',
45
48
  path.join(__dirname, '../node_modules')
46
49
  ]
47
50
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polendina",
3
- "version": "3.3.1",
3
+ "version": "3.3.3",
4
4
  "description": "Non-UI browser testing for JavaScript libraries from the command-line",
5
5
  "main": "polendina.js",
6
6
  "type": "module",
@@ -26,9 +26,8 @@
26
26
  },
27
27
  "scripts": {
28
28
  "lint": "standard",
29
- "test:install": "for f in $(cd test/fixtures/; ls); do (cd test/fixtures/$f && grep devDependencies package.json > /dev/null && npm i --no-audit --no-fund --no-package-lock) || true; done",
30
- "test:run": "npm_config_yes=true npx mocha test/test-*.js",
31
- "test": "npm run lint && npm run test:install && npm run test:run"
29
+ "test:run": "npx --yes mocha --require test/setup.js test/test-*.js",
30
+ "test": "npm run lint && npm run test:run"
32
31
  },
33
32
  "repository": {
34
33
  "type": "git",
package/polendina.js CHANGED
@@ -3,6 +3,7 @@ import path from 'path'
3
3
  import fs from 'fs'
4
4
  import http from 'http'
5
5
  import { fileURLToPath } from 'url'
6
+ import { createRequire } from 'module'
6
7
  import st from 'st'
7
8
  import { rimraf } from 'rimraf'
8
9
  import _webpack from 'webpack'
@@ -13,6 +14,9 @@ import { webpackConfig } from './lib/webpack.config.js'
13
14
  const webpack = promisify(_webpack)
14
15
 
15
16
  const __dirname = fileURLToPath(path.dirname(import.meta.url))
17
+ // polendina's own dependencies are found via Node resolution from this file,
18
+ // not by path: in a consumer install npm hoists them out of ./node_modules
19
+ const require = createRequire(import.meta.url)
16
20
 
17
21
  export class Polendina {
18
22
  constructor (options) {
@@ -44,14 +48,14 @@ export class Polendina {
44
48
  config = merge(config, {
45
49
  resolve: {
46
50
  fallback: {
47
- stream: path.join(__dirname, 'node_modules', 'stream-browserify'),
48
- path: path.join(__dirname, 'node_modules', 'path-browserify'),
51
+ stream: require.resolve('stream-browserify'),
52
+ path: require.resolve('path-browserify'),
49
53
  fs: false
50
54
  }
51
55
  },
52
56
  plugins: [
53
57
  new webpack.ProvidePlugin({
54
- process: 'process/browser'
58
+ process: require.resolve('process/browser')
55
59
  })
56
60
  ]
57
61
  })