polendina 3.3.0 → 3.3.2

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.
@@ -10,9 +10,9 @@ jobs:
10
10
  runs-on: ${{ matrix.os }}
11
11
  steps:
12
12
  - name: Checkout Repository
13
- uses: actions/checkout@v7
13
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
14
14
  - name: Use Node.js ${{ matrix.node }}
15
- uses: actions/setup-node@v7.0.0
15
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
16
16
  with:
17
17
  node-version: ${{ matrix.node }}
18
18
  - name: Install Dependencies
@@ -32,11 +32,11 @@ jobs:
32
32
  id-token: write
33
33
  steps:
34
34
  - name: Checkout
35
- uses: actions/checkout@v7
35
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
36
36
  with:
37
37
  fetch-depth: 0
38
38
  - name: Setup Node.js
39
- uses: actions/setup-node@v7.0.0
39
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
40
40
  with:
41
41
  node-version: lts/*
42
42
  registry-url: 'https://registry.npmjs.org'
package/AGENTS.md ADDED
@@ -0,0 +1,380 @@
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/` runs the CLI against `test/fixtures/*`, one real standalone package
106
+ per runner, in CJS and ESM, passing and failing.
107
+
108
+ ### The deferral trick
109
+
110
+ Test files must not run when the bundle loads. Mocha needs `describe()` calls
111
+ after `mocha.setup()`; tape needs its harness stream attached first.
112
+ `wrap-loader.cjs` replaces each test module with a stub that pushes
113
+ `{ name, load }` onto `test-registry.cjs`, where `load()` is a sync
114
+ `require('!!./file.js')`. The `*-run.js` entry, appended last, drains the
115
+ registry when ready. This is why the loader emits CJS, why the rule has
116
+ `type: 'javascript/auto'`, and why the registry and loader are `.cjs` in a
117
+ `"type": "module"` package.
118
+
119
+ ### Output channel
120
+
121
+ Browser `console.log` is overridden to call `polendinaLog(['info', seq, ...])`
122
+ through `page.exposeFunction`. Workers and ServiceWorkers forward through
123
+ `postMessage` to the page, which relays. Mocha's `BrowserStdout` polyfill of
124
+ `process.stdout.write` is detected by stack regex and routed to
125
+ `polendinaWrite` so partial lines stay partial. `lib/log.js` reorders by
126
+ sequence number because exposed-function calls can arrive out of order.
127
+ `end()` is debounced 100ms because `polendinaEnd` can beat trailing writes.
128
+ Touch any of this and run the full matrix on Windows and macOS CI before
129
+ believing it.
130
+
131
+ ## Fences
132
+
133
+ Things that look wrong and are not. Each exists because something broke.
134
+
135
+ - **`resolve.modules` and `resolveLoader.modules` list `process.cwd()` first,
136
+ polendina's `node_modules` second.** The consumer's mocha, tape and chai must
137
+ win. polendina has no mocha or tape in its own tree, on purpose.
138
+ - **Tape gets `resolve.fallback` for `stream` and `path`, `fs: false`, and
139
+ `ProvidePlugin({ process })`.** Webpack 5 dropped Node core polyfills.
140
+ Mocha's browser bundle is self-contained; tape's is not. `process`,
141
+ `path-browserify`, `stream-browserify` and `readable-stream` are
142
+ dependencies solely for this. They are resolved with `createRequire` from
143
+ `polendina.js`, never by path, because a hoisted consumer install has no
144
+ `polendina/node_modules` directory.
145
+ - **`import 'mocha/mocha.js'` for side effect, then `globalThis.mocha`.**
146
+ Mocha 11 ships UMD, 12 ships ESM. The global is the common denominator.
147
+ Both are supported at once. The `color()` / `useColors()` branch is the
148
+ same idea for older mochas.
149
+ - **`exprContextCritical: false`.** Mocha's bundle has a dynamic
150
+ `require.resolve`; without this every run prints a webpack warning.
151
+ - **`resolve.tsconfig: false`.** Webpack 5.109+ on Node 22.6+ follows a
152
+ dependency's `tsconfig.json`, which may `extends` a package that was never
153
+ published. Every resolve inside that dependency then fails.
154
+ - **Registry path in the loader is relative to the test file's directory,
155
+ forward-slashed.** Tests in subdirectories and Windows paths both broke the
156
+ cwd-relative version. Three separate fixes landed here. Paths in
157
+ `wrap-loader.cjs` are the most common regression site in the repo.
158
+ - **`puppeteer.launch({ args: ['--no-sandbox'], headless: 'new' })`.**
159
+ `--no-sandbox` for root in Docker and CI. `'new'` was a Puppeteer 20
160
+ deprecation fix and is itself a candidate for removal on current Puppeteer.
161
+ - **`st({ cache: false })`.** A stale cached bundle between page, worker and
162
+ serviceworker runs on Windows.
163
+ - **`setTimeout(...).unref()` for `--timeout`.** A clean finish must not be
164
+ held open; on fire it closes the browser and rejects.
165
+ - **`polendina-node` unwraps `mod['module.exports']`.** Node 23+ exposes CJS
166
+ loaded via `import()` under that named export; `bare-async` export
167
+ enumeration would otherwise see the wrong keys.
168
+ - **`polendina-cli` exits with the mocha failure count; `polendina-node` exits
169
+ 0 or 1.** The first matches mocha's own behaviour. Keep both.
170
+ - **`test:install` is a bash loop that `npm i`s every fixture with
171
+ `--no-package-lock`, and CI sets `script-shell bash` for Windows.** Each
172
+ fixture is a real package with its own devDependencies so the suite proves
173
+ fence one. mocha and tape must never become devDependencies of the root
174
+ package: they would land in polendina's `node_modules` and silently mask a
175
+ broken consumer resolve. `test:run` uses `npx mocha` for the same reason.
176
+ - **`test/common.js` asserts empty stderr on success and always passes
177
+ `--cleanup`.** Any stray warning is a test failure. Fixtures stay clean
178
+ between runs only because of the flag.
179
+ - **`chore` commits cut a patch release.** Every dependency bump ships. This is
180
+ deliberate: consumers get current Chrome and webpack without a human in the
181
+ loop.
182
+
183
+ ## Bitrot
184
+
185
+ What breaks, where it breaks, how it gets fixed. Expect each of these again.
186
+
187
+ | Trigger | Where it lands | Pattern |
188
+ |---------|----------------|---------|
189
+ | 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. |
190
+ | 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. |
191
+ | Puppeteer major | `lib/puppeteer.js` | Launch option deprecations. Usually a one-line change or nothing. |
192
+ | Node.js current | `polendina-node-cli.js`, anything ESM | Interop changes surface first on the `current` CI leg. |
193
+ | Any dependency going ESM-only | import sites | Named imports (`{ glob }`, `{ rimraf }`, `{ merge }`, `hideBin`). |
194
+ | 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. |
195
+ | Path separators | `lib/wrap-loader.cjs` | Windows. Normalise to forward slashes at the point of string generation. |
196
+
197
+ The CI matrix is `lts/*` and `current` on Linux, macOS and Windows. It is
198
+ there to catch these early, and it does. A red `current` leg on an otherwise
199
+ green matrix is a Node.js interop issue, not a flaky test.
200
+
201
+ ## Supported environment
202
+
203
+ - Node.js: Active and Maintenance lines on the official schedule. Dropping an
204
+ end-of-life line is a `chore`.
205
+ - Chrome: whatever the current Puppeteer ships. No other browser. Adding one
206
+ would be a new project.
207
+ - Test runners: mocha (default), tape, `bare-sync`, `bare-async`. Current and
208
+ previous major of mocha and tape at once, as the fixtures pin them.
209
+ - Consumer module format: CJS and ESM, both covered by fixtures.
210
+ - Platform: Linux, macOS, Windows. Windows is a first-class CI leg.
211
+
212
+ ## Testing polendina itself
213
+
214
+ ```bash
215
+ npm test # lint, install fixtures, run the matrix (slow)
216
+ npm run test:install # once, after a fixture package.json changes
217
+ npx mocha test/test-mocha.js # one runner's suite
218
+ npx mocha test/test-packed-install.js # the real install layout, needs a warm npm cache
219
+ cd test/fixtures/mocha && ../../../polendina-cli.js --cleanup test-*.js # by hand
220
+ cd test/fixtures/mocha && ../../../polendina-cli.js test-*.js --stats # keep build/ to inspect
221
+ ```
222
+
223
+ ### Install topologies
224
+
225
+ polendina resolves modules two ways at once: Node resolution for its own
226
+ imports, and webpack `resolve.modules` for the bundle, which checks the
227
+ consumer's `node_modules` then polendina's. Where "polendina's
228
+ `node_modules`" actually is depends on how it was installed, and no single
229
+ test setup covers all three cases.
230
+
231
+ | Topology | polendina's own `node_modules` | What it hides |
232
+ |----------|-------------------------------|---------------|
233
+ | Fixtures in this repo | The dev tree, two directories up | Fixtures can walk up and find polendina's dev dependencies. A missing fixture dependency, or any package added to the root, is silently masked. This is why mocha and tape must never be root dependencies. |
234
+ | 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. |
235
+ | 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. |
236
+
237
+ Consequences:
238
+
239
+ - A resolution change passing the fixture suite proves little on its own.
240
+ `test/test-packed-install.js` covers the published layout for tape and
241
+ mocha; extend it when a change touches resolution.
242
+ - Never reference polendina's own `node_modules` by path. Resolve through
243
+ Node from a file inside polendina so it works wherever npm put the package.
244
+ - The `webpack-merge` fixture resolves `webpack` by walking up to polendina's
245
+ tree, which stands in for a real consumer with no webpack of their own.
246
+ That is the fixture topology leaking, and it happens to match reality
247
+ because webpack is hoisted.
248
+
249
+ Rules the suite relies on:
250
+
251
+ - polendina is tested the way it is used: as a CLI run from inside a consumer
252
+ directory. Do not import `polendina.js` into tests to make them faster.
253
+ - Fixture `package.json` files are real and own their test framework.
254
+ - Every runner has a passing and a failing fixture, in CJS and ESM. A new
255
+ runner or mode adds all four. A new code path in an existing runner adds an
256
+ assertion to the existing fixtures rather than a new fixture.
257
+ - Expected output is matched as exact multi-line strings with timings
258
+ scrubbed. When output changes on purpose, update the expectation and say so
259
+ in the PR.
260
+ - Use `--stats` and skip `--cleanup` to inspect `build/bundle.js` and
261
+ `webpack-stats.json` when a resolve goes wrong.
262
+
263
+ ## Making a change
264
+
265
+ 1. Reproduce in a fixture first. If no fixture shows the problem, add the
266
+ smallest assertion to an existing one that does.
267
+ 2. Run that runner's suite, then the whole matrix. Windows and macOS only run
268
+ in CI, so push a branch and watch all six legs before calling it done.
269
+ 3. Try it against `cborg` or another real consumer, installed from
270
+ `npm pack`, not a symlink. For anything touching resolution, also run a
271
+ tape fixture through the packed install, since no consumer in the wild
272
+ exercises that path.
273
+ 4. For anything touching `lib/webpack.config.js`, `lib/wrap-loader.cjs` or
274
+ the output channel, state in the PR which fence you touched and why it
275
+ still holds.
276
+ 5. New flag or behaviour: README options list, CLI `describe`, fixture
277
+ coverage, in that order. Defaults never change in a minor.
278
+ 6. One concern per PR. No drive-by tidying.
279
+
280
+ ## Direction
281
+
282
+ For an agent asked to improve this project without a brief. The project is
283
+ healthy but high-entropy: many moving parts, each on its own release
284
+ schedule. Impact comes from reducing the number of things that can rot
285
+ without reducing what works.
286
+
287
+ Good work, roughly in order of value:
288
+
289
+ - **Keep the matrix green.** Pick up a red dependabot PR, find the fallout,
290
+ land the fix with the bump. This is the single most useful thing.
291
+ - **Remove dead weight with evidence.** `raw-loader` is a dependency no source
292
+ references. The `--coverage` flag and the `pti` TODO have never shipped. The
293
+ commented-out `page.on('console')` block is dead. The `useColors` branch is
294
+ unreachable on supported mochas. Each removal is one PR with a full test run
295
+ and a `git log -S` showing where it came from.
296
+ - **Move the fixtures into the packed consumer.** `test/test-packed-install.js`
297
+ already packs polendina and installs it into a temporary consumer outside
298
+ the repo, the only layout a real user has. Running the existing fixture
299
+ directories from inside that consumer would replace seventeen `npm i` runs
300
+ and seventeen dependabot entries with one install, close the topology leak
301
+ for every test, and make a second consumer on the previous mocha major
302
+ cheap. The fixtures and their assertions stay as they are; only where they
303
+ run changes.
304
+ - **Make failures legible.** When a resolve fails inside a consumer's
305
+ dependency, the webpack error is the user's only clue. Better messages
306
+ around bundle errors and the timeout are cheap wins.
307
+ - **Reduce the webpack surface.** Fewer config keys, fewer polyfills, fewer
308
+ reasons for a webpack release to break a run. If the tape polyfill set can
309
+ shrink, shrink it. Node core modules like `assert` are not polyfilled at
310
+ all, and that is deliberate; fixtures use chai.
311
+ - **Evaluate replacing webpack.** esbuild or rolldown could bundle, with a
312
+ plugin doing the wrap-loader's job. This is a major: `--webpack-config` is a
313
+ documented extension point used in the wild for externals and TypeScript.
314
+ Any replacement needs an equivalent escape hatch. It is telos-compatible
315
+ and would remove the biggest source of bitrot. Write up the trade before
316
+ writing code, and keep the CLI, output and fixtures identical.
317
+
318
+ Work to avoid:
319
+
320
+ - Any change that makes the consumer add a file or a flag to get what they
321
+ get today, or that removes an override they rely on.
322
+ - Adding a second browser, UI testing, screenshots or coverage.
323
+ - Pinning dependencies to dodge a break. Fix the break.
324
+ - Adding mocha or tape to the root package in any form.
325
+ - Rewriting the output channel for elegance. It is shaped by CI on three
326
+ platforms and the shape is the fix.
327
+ - Touching the release config, dependabot config or CI matrix unless that is
328
+ the point of the PR.
329
+
330
+ ## Code style
331
+
332
+ - StandardJS: no semicolons, single quotes, space before function parens.
333
+ Fix lint, never disable it.
334
+ - Plain JavaScript, ESM in the package, `.cjs` only where webpack demands it.
335
+ No TypeScript, no build step.
336
+ - Comments describe the code as it is now. Never what changed, why a line was
337
+ added or which issue it fixed; that belongs in the commit. Every comment
338
+ earns its place and its word count.
339
+ - Simple language in prose, docs and messages. Short sentences. Plain
340
+ punctuation: no em or en dashes, no emoji outside the existing log brand.
341
+ - Nothing new at the repository root. Node-side code in `lib/`, browser-side
342
+ in `resources/`, tests and fixtures in `test/`.
343
+ - A new runtime dependency needs a sentence in the PR saying why the consumer
344
+ should carry it.
345
+
346
+ ## Commits and pull requests
347
+
348
+ Conventional commits drive semantic-release on every push to `master`.
349
+
350
+ | Type | Release |
351
+ |------|---------|
352
+ | `feat` | minor |
353
+ | `fix`, `perf`, `chore`, `docs`, `test`, `revert` | patch |
354
+ | `refactor`, `ci`, `build`, `style` | none |
355
+ | any type with `!` and a `BREAKING CHANGE:` footer | major |
356
+ | scope `no-release` | none |
357
+
358
+ - Subject: imperative, lower case, no trailing period, under 72 characters.
359
+ Scopes in use: `mocha`, `deps`, `ci`.
360
+ - Terse and undecorated. No labels, badges or boilerplate.
361
+ - A breaking change says who is affected and what they must do. Dropping a
362
+ mocha or Node.js major is breaking even when the blast radius is small.
363
+ - PRs are squash-merged and the title becomes the commit subject. History is
364
+ linear.
365
+ - `CHANGELOG.md` and the version in `package.json` are generated. Never edit
366
+ them.
367
+
368
+ ## Keep this file true
369
+
370
+ When a change makes anything here inaccurate (a file moved, a flag changed, a
371
+ fence removed, a bitrot pattern resolved for good), update this file in the
372
+ same PR. Describe the project as it is. No history, no change notes.
373
+
374
+ ## References
375
+
376
+ - Puppeteer: https://pptr.dev/
377
+ - Webpack resolve options: https://webpack.js.org/configuration/resolve/
378
+ - Webpack loader pitching: https://webpack.js.org/api/loaders/#pitching-loader
379
+ - Mocha browser usage: https://mochajs.org/#running-mocha-in-the-browser
380
+ - Reference consumer, `cborg`: https://github.com/rvagg/cborg
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [3.3.2](https://github.com/rvagg/polendina/compare/v3.3.1...v3.3.2) (2026-10-24)
2
+
3
+ ### Bug Fixes
4
+
5
+ * **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))
6
+
7
+ ### Trivial Changes
8
+
9
+ * add agent context files ([#204](https://github.com/rvagg/polendina/issues/204)) ([e822ab3](https://github.com/rvagg/polendina/commit/e822ab366f6b9a6ade72ab2e93ca08952b23e56a))
10
+
11
+ ## [3.3.1](https://github.com/rvagg/polendina/compare/v3.3.0...v3.3.1) (2026-10-19)
12
+
13
+ ### Trivial Changes
14
+
15
+ * **deps:** bump actions/setup-node from 7.0.0 to 7.1.0 ([#202](https://github.com/rvagg/polendina/issues/202)) ([72fbc6f](https://github.com/rvagg/polendina/commit/72fbc6f88df2ec9341e545c8fa7fdcbc85e9fd24))
16
+
1
17
  ## [3.3.0](https://github.com/rvagg/polendina/compare/v3.2.26...v3.3.0) (2026-09-21)
2
18
 
3
19
  ### Features
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polendina",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
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.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
  })
@@ -0,0 +1,87 @@
1
+ /* globals describe it before after */
2
+
3
+ // Installs polendina from a packed tarball into a consumer outside this
4
+ // repository and runs the CLI there. npm hoists polendina's dependencies into
5
+ // the consumer's node_modules, a layout the in-repo fixtures never produce.
6
+
7
+ import assert from 'assert'
8
+ import fs from 'fs'
9
+ import os from 'os'
10
+ import path from 'path'
11
+ import { exec, execFile } from 'child_process'
12
+ import { fileURLToPath } from 'url'
13
+
14
+ const __dirname = fileURLToPath(path.dirname(import.meta.url))
15
+ const root = path.join(__dirname, '..')
16
+ const puppeteerVersion = JSON.parse(fs.readFileSync(path.join(root, 'node_modules/puppeteer/package.json'))).version
17
+
18
+ function sh (command, cwd) {
19
+ return new Promise((resolve, reject) => {
20
+ exec(command, { cwd, env: { ...process.env, PUPPETEER_SKIP_DOWNLOAD: '1' } }, (err, stdout, stderr) => {
21
+ if (err) {
22
+ return reject(new Error(`${command}\n${stderr}`))
23
+ }
24
+ resolve(stdout.toString())
25
+ })
26
+ })
27
+ }
28
+
29
+ function cli (cwd, args) {
30
+ return new Promise((resolve) => {
31
+ const bin = path.join(cwd, 'node_modules/polendina/polendina-cli.js')
32
+ execFile(process.execPath, [bin, ...args], { cwd }, (err, stdout, stderr) => {
33
+ resolve({ code: err ? err.code : 0, stdout: stdout.toString(), stderr: stderr.toString() })
34
+ })
35
+ })
36
+ }
37
+
38
+ describe('packed install', function () {
39
+ this.timeout(300000)
40
+ let consumer
41
+
42
+ before(async () => {
43
+ consumer = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'polendina-consumer-'))
44
+ const [{ filename }] = JSON.parse(await sh(`npm pack --json --pack-destination "${consumer}"`, root))
45
+ await fs.promises.writeFile(path.join(consumer, 'package.json'), JSON.stringify({ name: 'consumer', version: '1.0.0', private: true }))
46
+ await fs.promises.writeFile(path.join(consumer, 'test-tape.js'), `
47
+ const tape = require('tape')
48
+ tape('packed', (t) => { t.ok(true, 'runs'); t.end() })
49
+ `)
50
+ await fs.promises.writeFile(path.join(consumer, 'test-mocha.js'), `
51
+ /* globals describe it */
52
+ describe('packed', () => { it('runs', () => { if (!true) throw new Error('nope') }) })
53
+ `)
54
+ await sh(`npm install --no-audit --no-fund --no-package-lock --prefer-offline "./${filename}" tape mocha puppeteer@${puppeteerVersion}`, consumer)
55
+ })
56
+
57
+ after(async () => {
58
+ if (consumer) {
59
+ await fs.promises.rm(consumer, { recursive: true, force: true })
60
+ }
61
+ })
62
+
63
+ it('hoists dependencies', async () => {
64
+ await assert.rejects(fs.promises.access(path.join(consumer, 'node_modules/polendina/node_modules/stream-browserify')))
65
+ await fs.promises.access(path.join(consumer, 'node_modules/stream-browserify'))
66
+ })
67
+
68
+ it('runs tape', async () => {
69
+ const { code, stdout, stderr } = await cli(consumer, ['--runner=tape', '--cleanup', 'test-tape.js'])
70
+ if (code !== 0) {
71
+ console.error(stdout, stderr)
72
+ }
73
+ assert.strictEqual(code, 0, 'exited with zero exit code')
74
+ assert.strictEqual(stderr, '', 'no stderr')
75
+ assert.ok(stdout.includes('ok 1 runs'), 'stdout contains tape output')
76
+ })
77
+
78
+ it('runs mocha', async () => {
79
+ const { code, stdout, stderr } = await cli(consumer, ['--cleanup', 'test-mocha.js'])
80
+ if (code !== 0) {
81
+ console.error(stdout, stderr)
82
+ }
83
+ assert.strictEqual(code, 0, 'exited with zero exit code')
84
+ assert.strictEqual(stderr, '', 'no stderr')
85
+ assert.ok(stdout.includes('1 passing'), 'stdout contains mocha output')
86
+ })
87
+ })