polendina 3.3.1 → 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.
- package/AGENTS.md +380 -0
- package/CHANGELOG.md +10 -0
- package/CLAUDE.md +1 -0
- package/package.json +1 -1
- package/polendina.js +7 -3
- package/test/test-packed-install.js +87 -0
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,13 @@
|
|
|
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
|
+
|
|
1
11
|
## [3.3.1](https://github.com/rvagg/polendina/compare/v3.3.0...v3.3.1) (2026-10-19)
|
|
2
12
|
|
|
3
13
|
### Trivial Changes
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/package.json
CHANGED
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:
|
|
48
|
-
path:
|
|
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
|
+
})
|