@unotest/web 0.39.0 → 0.41.0
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/CHANGELOG.md +588 -3
- package/README.md +24 -5
- package/dist/config/schema.js +1 -1
- package/dist/driver/index.js +1 -1
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/web-dsl-language-service.js +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/runner/cli.js +1 -1
- package/dist/runner/init/chrome-probe.js +1 -1
- package/dist/runner/init.d.ts +5 -0
- package/dist/runner/init.js +1 -1
- package/dist/runner/install-chromium.js +1 -1
- package/dist/runner/prepare-fix.js +1 -1
- package/dist/runner/scaffold-workspace.js +1 -1
- package/dist/runner/serve-fixture.js +1 -1
- package/dist/runner/web-runner-adapter.js +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,590 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.41.0] - 2026-09-21
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e793fa2: `env push | set | rm`: the box and the token come from the project's own files
|
|
8
|
+
|
|
9
|
+
The values commands now resolve the box address and the project token
|
|
10
|
+
exactly the way `bundle push` does — a flag, then `UNOTEST_BOX_URL` /
|
|
11
|
+
`UNOTEST_BOX_TOKEN`, then `unotest/.env` and `unotest/.secrets`, where
|
|
12
|
+
`box create` and `box access` wrote them. A project set up with either of
|
|
13
|
+
those commands no longer answers "no box address" to `env push` while
|
|
14
|
+
`bundle push` works, and the two exports stop being typed in front of
|
|
15
|
+
every line.
|
|
16
|
+
|
|
17
|
+
`env set <env> <NAME> <value>` takes the value as an argument. Stdin
|
|
18
|
+
still works when there is none, and stays the way to pass a credential:
|
|
19
|
+
an argument is in the shell's history.
|
|
20
|
+
|
|
21
|
+
`env push --dry-run` now prints what the overlays resolved to, quoted, so
|
|
22
|
+
a value that kept its quotes or picked up a trailing space is visible
|
|
23
|
+
before it reaches a box. `--dry-run --json` carries the same under
|
|
24
|
+
`values`. A secret's value is still never printed: where it would be, the
|
|
25
|
+
mask used everywhere else (`‹secret:NAME›`) stands instead.
|
|
26
|
+
|
|
27
|
+
A push whose box lacks some of the suite's declared values no longer
|
|
28
|
+
claims the run "will stop at the first step that reads" one of them: the
|
|
29
|
+
names are what the suite DECLARES, and a declared name no scenario reads
|
|
30
|
+
costs a run nothing.
|
|
31
|
+
|
|
32
|
+
- 41b0369: A box rented from the dashboard now says how to send it a suite
|
|
33
|
+
|
|
34
|
+
A box rented from the CLI arrives with the suite already on it: `box create`
|
|
35
|
+
pushes it and prints the command that runs it. A box rented from the dashboard
|
|
36
|
+
arrives empty, and the card said nothing about that. It showed the address, the
|
|
37
|
+
login and the password, and left the customer with a machine that answers, an
|
|
38
|
+
empty viewer, and no idea that anything else was needed. Everything they had to
|
|
39
|
+
do next lived in an email and on the box's own empty screen.
|
|
40
|
+
|
|
41
|
+
The card of a ready box now carries the three commands that take a project from
|
|
42
|
+
here to a first run: sign in, take the box's tokens, push the suite. Each is a
|
|
43
|
+
field that can be copied in one click, with the box's own name already in it.
|
|
44
|
+
The environment name is filled in when the box has exactly one, and left as
|
|
45
|
+
`<env>` when it has several — picking one for the customer would send the run
|
|
46
|
+
to a site they did not mean, and nothing would refuse it, because the name is
|
|
47
|
+
perfectly valid. The collection is named too: a box runs a collection, not a
|
|
48
|
+
single scenario, and a push that names none is turned away.
|
|
49
|
+
|
|
50
|
+
Those commands are now written in one place and printed by both the card and
|
|
51
|
+
the "your box is ready" letter. They used to be the letter's alone, and a
|
|
52
|
+
second copy on the card would have been a second piece of advice the first time
|
|
53
|
+
either was edited.
|
|
54
|
+
|
|
55
|
+
Once a project has taken the box's tokens, the card stops repeating the first
|
|
56
|
+
two commands and asks only for the push. That is the one thing the cloud can
|
|
57
|
+
honestly know about the far end of a box: the suite travels from the customer's
|
|
58
|
+
machine straight to it, and nothing reports back. So the card says nothing at
|
|
59
|
+
all about runs — not "no runs yet", not "0 runs". It cannot tell a box that has
|
|
60
|
+
been testing for a week from one nobody has touched, and a page that says
|
|
61
|
+
otherwise is inventing it.
|
|
62
|
+
|
|
63
|
+
### Patch Changes
|
|
64
|
+
|
|
65
|
+
- 36bc4b3: An empty list of environments says which kind of empty it is
|
|
66
|
+
|
|
67
|
+
`box envs` on a box that listed nothing said "This box serves no environments
|
|
68
|
+
you can read." — a sentence about the token, on a list that is not filtered by
|
|
69
|
+
the token at all. People whose box was simply new went looking for a stronger
|
|
70
|
+
credential, and the box's own journal had nothing to say either way.
|
|
71
|
+
|
|
72
|
+
The list now talks about the box:
|
|
73
|
+
|
|
74
|
+
This box serves no environments yet.
|
|
75
|
+
An environment appears once a suite has been pushed to it.
|
|
76
|
+
|
|
77
|
+
A token that really is not let in still answers in its own words, with its own
|
|
78
|
+
exit code, so the two are told apart by reading them.
|
|
79
|
+
|
|
80
|
+
Behind the list, the box now records which of five things happened when it
|
|
81
|
+
looked for its route table: there is no table yet, the table cannot be read,
|
|
82
|
+
it is not JSON, its shape is wrong, or it is honestly empty. Each is one line
|
|
83
|
+
in the box's journal, naming the file and never its contents — the table holds
|
|
84
|
+
internal addresses. A table that loaded says how many environments it found,
|
|
85
|
+
because "none" and "three" were otherwise the same silence.
|
|
86
|
+
|
|
87
|
+
Two fixes come with it. A line belongs to a state rather than to a request, so
|
|
88
|
+
a busy box writes one line and not one per proxied asset. And a table that
|
|
89
|
+
failed to parse is read again a second later instead of being remembered as an
|
|
90
|
+
empty box until its timestamp changes — which is why "I tried again and it was
|
|
91
|
+
the same" used to be true.
|
|
92
|
+
|
|
93
|
+
An empty list stays a successful answer, `200` with no entries: a box with
|
|
94
|
+
nothing on it yet is a normal box, not a service that is down.
|
|
95
|
+
|
|
96
|
+
- 7bac819: A box installs a suite whose `npm ci` is slow, and stops retrying in a loop
|
|
97
|
+
|
|
98
|
+
A box could not install a suite whose dependency install went quiet for
|
|
99
|
+
more than five minutes — which is what a heavy lockfile does. The install
|
|
100
|
+
died on the 301st second, reported the runner as unavailable, and the box
|
|
101
|
+
started the whole install again in the second it failed. That repeated for
|
|
102
|
+
as long as the box existed: nothing ever ran on it, and the message blamed
|
|
103
|
+
the network rather than naming the timeout that caused it.
|
|
104
|
+
|
|
105
|
+
Three things change. The daemon no longer puts a ceiling of its own on
|
|
106
|
+
reading a run's output, so silence is not a failure; the run's deadline
|
|
107
|
+
remains the only clock that ends a run, and a backstop behind it covers a
|
|
108
|
+
connection that stops answering altogether. A run whose output stops
|
|
109
|
+
without an exit now takes its container with it — abandoned containers
|
|
110
|
+
used to fill the box's capacity while healthy environments waited for a
|
|
111
|
+
slot. And a failed install is followed by a pause that grows: 1, 2, 5, 15
|
|
112
|
+
and 30 minutes, capped there, never "never". Asking for that bundle
|
|
113
|
+
yourself — a switch or a repair — is tried at once and clears the count,
|
|
114
|
+
as does an install that succeeds, and the log says when the box will try
|
|
115
|
+
next by itself.
|
|
116
|
+
|
|
117
|
+
The pause is the box's own, never yours: a run you order by hand, and a run
|
|
118
|
+
a push to your repository orders, install the bundle at once and clear the
|
|
119
|
+
count. Only the box's unattended retry waits.
|
|
120
|
+
|
|
121
|
+
- 7880b7d: Installs on a box share one npm cache, and a suite can never reach it
|
|
122
|
+
|
|
123
|
+
Until now every install on a box started with an empty npm cache: the
|
|
124
|
+
container that runs `npm ci` was thrown away with everything it had
|
|
125
|
+
downloaded, so the second box of the same account — and the same box
|
|
126
|
+
after a restart — paid the full download again for dependencies it had
|
|
127
|
+
already had twice.
|
|
128
|
+
|
|
129
|
+
Boxes now keep one npm cache and every install uses it. The second
|
|
130
|
+
install of a dependency is taken from disk instead of the network; how
|
|
131
|
+
much time that saves depends on your lockfile and your registry, and on a
|
|
132
|
+
fast registry it can be none at all — the promise here is only that
|
|
133
|
+
what has already been downloaded is not downloaded again.
|
|
134
|
+
|
|
135
|
+
The cache is mounted into the container that installs, and into no other.
|
|
136
|
+
A container running your suite gets no access to it under any name: the
|
|
137
|
+
cache is the directory that every later install of every project on that
|
|
138
|
+
box unpacks, and a test that could write into it would be writing into
|
|
139
|
+
the next install. That boundary is asserted as a pair — the install has
|
|
140
|
+
it, the run does not — rather than as two separate claims.
|
|
141
|
+
|
|
142
|
+
A substituted cache entry is never installed. npm verifies an entry's
|
|
143
|
+
checksum when it READS from the cache, not only when it writes one: an
|
|
144
|
+
entry whose content does not match is reported as corrupted and fetched
|
|
145
|
+
again (verified on the box's own image, npm 11.16.0 — with a registry
|
|
146
|
+
reachable the correct package is installed; without one the install fails
|
|
147
|
+
rather than using the substitute).
|
|
148
|
+
|
|
149
|
+
The box keeps the cache's size in hand. It is measured on each
|
|
150
|
+
housekeeping sweep and, over the ceiling (1 GB by default, and an
|
|
151
|
+
operator can raise it or turn it off), emptied whole with a line in the
|
|
152
|
+
log — losing a cache costs a download and nothing else.
|
|
153
|
+
|
|
154
|
+
- d456001: Ordering a run in an environment now decides what that environment serves
|
|
155
|
+
|
|
156
|
+
Pushing a suite and asking for a run in a named environment is a
|
|
157
|
+
statement about that environment: it should serve the bundle you just
|
|
158
|
+
sent. Until now the box decided that separately, by matching the
|
|
159
|
+
bundle's branch — so a suite packed outside a git repository, which is
|
|
160
|
+
what `init` produces in a clean directory, matched nothing. The run
|
|
161
|
+
installed, executed and passed, while the environment kept pointing at
|
|
162
|
+
nothing: its viewer was never started, the guard was given no route for
|
|
163
|
+
it, `box envs` listed nothing, and `box runs --env <name>` answered that
|
|
164
|
+
the box does not serve that environment. Four different answers to one
|
|
165
|
+
question, after the client had been told the suite was accepted and
|
|
166
|
+
queued.
|
|
167
|
+
|
|
168
|
+
An explicit run request now selects the bundle for the environment,
|
|
169
|
+
through the same path a selection made in the viewer takes, recorded
|
|
170
|
+
against the token that pushed — so it survives a restart and the box
|
|
171
|
+
does not silently move the environment back on its next sweep. Branch
|
|
172
|
+
matching keeps deciding exactly one thing, the one it is right for:
|
|
173
|
+
which bundle the environment's SCHEDULES run.
|
|
174
|
+
|
|
175
|
+
An environment that may not be switched (`allowSwitch: false`) keeps
|
|
176
|
+
serving what its own branch deploys, and the answer now says so instead
|
|
177
|
+
of leaving it to be found out: the run goes ahead there, and the
|
|
178
|
+
acceptance names the branch the environment stays on — so it is clear
|
|
179
|
+
the result belongs to that environment's journal rather than to the
|
|
180
|
+
screen the environment shows.
|
|
181
|
+
|
|
182
|
+
The one case with nothing to show is refused out loud. An environment
|
|
183
|
+
that may not be switched and serves nothing yet has no viewer at all, so
|
|
184
|
+
a run ordered there could only end where this bug ended: executed, green
|
|
185
|
+
and invisible. The refusal names the branch to deploy to instead.
|
|
186
|
+
|
|
187
|
+
Migration note: that refusal is a new error code on the run endpoint.
|
|
188
|
+
A CLI older than this release does not know it and will report the
|
|
189
|
+
box's answer as "not a box" rather than as a refusal — the request is
|
|
190
|
+
still refused, and nothing runs. Upgrade the CLI to see the message.
|
|
191
|
+
The acceptance note is a new field on an otherwise unchanged answer, and
|
|
192
|
+
an older CLI ignores it.
|
|
193
|
+
|
|
194
|
+
- 436b683: The box's own screen gives the same first-run command as everything else
|
|
195
|
+
|
|
196
|
+
A freshly rented box shows two commands: one that points the project at the
|
|
197
|
+
box, one that sends the suite and starts it running. The second was printed
|
|
198
|
+
without `--collection`, and a box runs a collection or a schedule — never a
|
|
199
|
+
scenario by name. Copied exactly as shown, into a project straight out of
|
|
200
|
+
`init`, it came back refused with nothing to run: an answer about a project
|
|
201
|
+
that had done nothing wrong, from the first screen a new box owner reads.
|
|
202
|
+
|
|
203
|
+
The screen now prints the whole command, and says where collections live:
|
|
204
|
+
|
|
205
|
+
npx @unotest/web bundle push --run --env ENVIRONMENT --collection examples
|
|
206
|
+
|
|
207
|
+
The command is no longer written out on each surface that shows it. The box's
|
|
208
|
+
screen, the last line of `box create`, and the collection `init` writes all
|
|
209
|
+
come from one place, so the next flag to change changes in one edit rather
|
|
210
|
+
than in three. `examples` is the collection `init` creates, and the screen
|
|
211
|
+
says so — it is an example of a name, not a claim about your project.
|
|
212
|
+
|
|
213
|
+
The cabinet half is unchanged in behaviour: with exactly one environment and
|
|
214
|
+
one collection it still prints a command that can be pasted as is, and
|
|
215
|
+
placeholders where it cannot know.
|
|
216
|
+
|
|
217
|
+
- 3d8e45d: `unotest/.secrets` is now created owner-only (`0600`)
|
|
218
|
+
|
|
219
|
+
The file holds the box push and read tokens, and was created with the
|
|
220
|
+
default umask — on a shared machine any other user could read it. It is
|
|
221
|
+
now narrow from the moment it is created, and a file left wider by an
|
|
222
|
+
earlier version is narrowed the next time a command writes to it, with one
|
|
223
|
+
line saying what changed. `unotest/.env` is configuration rather than
|
|
224
|
+
secrets and keeps the width your project gave it.
|
|
225
|
+
|
|
226
|
+
- 8a436b9: Exploration tools refuse an unknown `{{NAME}}` instead of typing it
|
|
227
|
+
|
|
228
|
+
A `{{NAME}}` the session could not resolve was passed through as text, so
|
|
229
|
+
`explore_run_flow {args:["admin","{{PASSWORD}}"]}` typed the placeholder
|
|
230
|
+
into the password field and the page answered with a normal login failure.
|
|
231
|
+
It now fails with a message that names the variable, says where to declare
|
|
232
|
+
it, and lists the names the session does have — so the value never has to
|
|
233
|
+
be pasted into the tool call by hand.
|
|
234
|
+
|
|
235
|
+
A bare `SHOUTY_NAME` is unchanged: a reference where it is registered, a
|
|
236
|
+
literal otherwise. A variable declared with an empty value still
|
|
237
|
+
substitutes as empty and is reported in `variableWarnings`; a name
|
|
238
|
+
declared as a secret with an empty value is refused with its own message.
|
|
239
|
+
|
|
240
|
+
- a1b9eac: Editing a secret in the viewer's variables panel keeps `unotest/.secrets` owner-only
|
|
241
|
+
|
|
242
|
+
The panel writes a file by creating a temporary one beside it and renaming
|
|
243
|
+
it into place, and a rename replaces the target together with its
|
|
244
|
+
permissions. So a secrets file that was owner-only became world-readable
|
|
245
|
+
again after a single edit in the panel, with the box tokens inside it, and
|
|
246
|
+
adding the first secret to a project created the file wide in the first
|
|
247
|
+
place.
|
|
248
|
+
|
|
249
|
+
The temporary file is now created owner-only when the layer being written
|
|
250
|
+
holds secrets, which also narrows a file an earlier version left wide. A
|
|
251
|
+
leftover temporary file from an interrupted write is removed rather than
|
|
252
|
+
reused, so it cannot pass its own permissions on. `unotest/.env` is
|
|
253
|
+
configuration rather than secrets and keeps the width your project gave it.
|
|
254
|
+
On Windows there are no POSIX permission bits and nothing is narrowed
|
|
255
|
+
there.
|
|
256
|
+
|
|
257
|
+
- Updated dependencies [436b683]
|
|
258
|
+
- Updated dependencies [a1b9eac]
|
|
259
|
+
- @unotest/protocol@0.41.0
|
|
260
|
+
- @unotest/viewer@0.41.0
|
|
261
|
+
- @unotest/core@0.41.0
|
|
262
|
+
- @unotest/dsl@0.41.0
|
|
263
|
+
- @unotest/grounder-client@0.41.0
|
|
264
|
+
|
|
265
|
+
## [0.40.0] - 2026-09-19
|
|
266
|
+
|
|
267
|
+
### Minor Changes
|
|
268
|
+
|
|
269
|
+
- fa848e9: A box that finishes coming up tells the person who rented it
|
|
270
|
+
|
|
271
|
+
Renting a box takes minutes, and until now it ended in silence. Somebody who
|
|
272
|
+
closed the tab while the machine was being built learned nothing: not that it
|
|
273
|
+
was up, not where it answers, not what to do next. A rental that says nothing
|
|
274
|
+
looks like a rental that did not work.
|
|
275
|
+
|
|
276
|
+
The service now sends one message, at the moment the box becomes ready, to the
|
|
277
|
+
address on the account. It names the box's address, points at the card on the
|
|
278
|
+
dashboard where the login and password stay readable for as long as the box
|
|
279
|
+
lives, and gives the three commands that put a suite on it:
|
|
280
|
+
|
|
281
|
+
npx @unotest/web login
|
|
282
|
+
npx @unotest/web box access <box>
|
|
283
|
+
npx @unotest/web bundle push --run --env <environment> --collection examples
|
|
284
|
+
|
|
285
|
+
The last one names a collection because a box runs a collection or a schedule,
|
|
286
|
+
never a scenario by name, and a project straight out of `init` has no schedule:
|
|
287
|
+
without the flag the run is refused before it starts. `examples` is what `init`
|
|
288
|
+
writes, and the message says to name your own instead where you have one.
|
|
289
|
+
|
|
290
|
+
The environment is filled in only when the box has one. A box can have several
|
|
291
|
+
— `box create` sends every environment the project has — and a command naming
|
|
292
|
+
the first of them reads as a command naming the only one, which would send a
|
|
293
|
+
run somewhere the reader did not choose. With several, the command carries
|
|
294
|
+
`<env>` and the message says where the names are written. Same rule as the
|
|
295
|
+
CLI's own next step, so the two do not answer one question differently.
|
|
296
|
+
|
|
297
|
+
The message carries no password. The break-glass password of a box is on its
|
|
298
|
+
card, which can be looked at again; a copy in a mailbox is one nobody can
|
|
299
|
+
withdraw. The type the message is built from has no field for it, so it cannot
|
|
300
|
+
be added back by accident.
|
|
301
|
+
|
|
302
|
+
It is sent once. The guard is the transition itself, taken inside the write
|
|
303
|
+
lock that makes a box ready, so a resumed build, the billing tick and a read
|
|
304
|
+
of the card all find the box already ready and announce nothing. A mail server
|
|
305
|
+
that is down is logged and does not touch the rental: the customer paid for a
|
|
306
|
+
machine, not for mail.
|
|
307
|
+
|
|
308
|
+
- 42548f7: A box deleted by hand tells its owner too, and the letter says which deletion happened
|
|
309
|
+
|
|
310
|
+
A box could go two ways, and only one of them said anything. When the balance
|
|
311
|
+
stopped covering a box's next hour, the billing tick deleted it and wrote to
|
|
312
|
+
the owner. When a person deleted one — from the dashboard, the CLI or the REST
|
|
313
|
+
API — the machine was released, the record was settled, and nobody was told.
|
|
314
|
+
|
|
315
|
+
That silence is only harmless while the person who presses delete and the
|
|
316
|
+
person who pays for the account are the same person. An account can have
|
|
317
|
+
several people on it, an API key, and a dashboard open where it should not be.
|
|
318
|
+
The one deletion a customer most needs to hear about is the one they did not
|
|
319
|
+
make, and it was the one that arrived as nothing at all.
|
|
320
|
+
|
|
321
|
+
Every deletion now ends in the same letter, from the same place, and the
|
|
322
|
+
letter's first paragraph says how the box went: the balance could not cover
|
|
323
|
+
the next hour, or somebody with access to the account asked for it. The second
|
|
324
|
+
version names no sum — what the balance happened to be when a person pressed
|
|
325
|
+
delete explains nothing about why the box is gone, and a figure printed beside
|
|
326
|
+
a deletion reads as its cause. It points at the dashboard instead, for a reader
|
|
327
|
+
whose next question is who else has access. Everything after that first
|
|
328
|
+
paragraph is one text for both: the machine and what was stored on it are gone,
|
|
329
|
+
the scenarios in the customer's own repository are not, and the name is never
|
|
330
|
+
reused.
|
|
331
|
+
|
|
332
|
+
One letter per deletion, not per caller. The transition to deleted is claimed
|
|
333
|
+
under the same write lock that settles the record, and only the caller that
|
|
334
|
+
made it writes. A person and the billing tick arriving at one box in the same
|
|
335
|
+
moment therefore send one letter between them — previously they could each
|
|
336
|
+
send their own.
|
|
337
|
+
|
|
338
|
+
A box the fleet has no record of is now settled rather than retried forever.
|
|
339
|
+
The tick used to treat "no such box" from the fleet as a failure to delete,
|
|
340
|
+
leaving the record live and billable and trying again every hour; it now ends
|
|
341
|
+
the way it does when a person deletes such a box, which is that there is
|
|
342
|
+
nothing to release and the record is closed.
|
|
343
|
+
|
|
344
|
+
The journal says which way a box went, on both paths. Until now a deletion by
|
|
345
|
+
hand left no line at all, and the only way to tell one from an eviction was the
|
|
346
|
+
absence of the tick's own lines.
|
|
347
|
+
|
|
348
|
+
A mail server that is down still never holds a deletion up: the machine is
|
|
349
|
+
released and the record settled first, and a failed send is a line in the log.
|
|
350
|
+
|
|
351
|
+
- cb4a7e5: `box access` without a name lists the boxes the account already has, instead of advising a second one
|
|
352
|
+
|
|
353
|
+
Run in a directory that does not know a box — a fresh checkout, a second
|
|
354
|
+
project — `box access`, `box status`, `box destroy` and `box picker`
|
|
355
|
+
refused with three things to try, and the only one anybody can follow
|
|
356
|
+
without thinking was `box create`. For somebody who already rents a box,
|
|
357
|
+
following it rents and pays for another.
|
|
358
|
+
|
|
359
|
+
The refusal now asks the account first. With one box it names it and gives
|
|
360
|
+
the command back ready to run:
|
|
361
|
+
|
|
362
|
+
no box named and none configured — this account has one box,
|
|
363
|
+
"larch": run `box access larch`
|
|
364
|
+
|
|
365
|
+
With several it lists them and asks which. Only an account with no boxes
|
|
366
|
+
at all is still told to create one, and a cloud that refused or could not
|
|
367
|
+
be reached is told the same as before — a cloud that did not answer is
|
|
368
|
+
never read as "you have no boxes".
|
|
369
|
+
|
|
370
|
+
- 1cb7ba5: Reading a box takes the same environment name that ordered the run
|
|
371
|
+
|
|
372
|
+
`bundle push --run --env prod` has always taken the bare environment
|
|
373
|
+
name and let the box fill the project in from the token. The commands
|
|
374
|
+
and MCP tools that read the results back wanted the full
|
|
375
|
+
`acme/prod`, and a bare name came back as a 404 that talked about the
|
|
376
|
+
environment rather than about its form:
|
|
377
|
+
|
|
378
|
+
✗ this box does not serve 'prod'
|
|
379
|
+
|
|
380
|
+
So the name that worked in one command failed in the next, and nothing
|
|
381
|
+
on screen said why.
|
|
382
|
+
|
|
383
|
+
Both halves now take both spellings. `box runs`, `box run`, `box queue`
|
|
384
|
+
and `box screenshot` — and `box_runs`, `box_run`, `box_run_download`,
|
|
385
|
+
`box_queue`, `box_screenshot` — translate the name against the box's own
|
|
386
|
+
list before the request, exactly the list `box envs` prints. A bare name
|
|
387
|
+
only one project on the box has is sent as that project's full name; a
|
|
388
|
+
bare name several projects share is refused before anything is asked,
|
|
389
|
+
naming them:
|
|
390
|
+
|
|
391
|
+
✗ box runs: more than one project on this box has an environment
|
|
392
|
+
called "test" (acme/test, beta/test) — name the one you mean in
|
|
393
|
+
full.
|
|
394
|
+
|
|
395
|
+
A name the box serves in neither form is refused the same way, listing
|
|
396
|
+
what it does serve. Where the list cannot be had — a box that did not
|
|
397
|
+
answer, a refused token, an empty list, which does not prove there are
|
|
398
|
+
no environments — the name goes as it was typed and the box decides,
|
|
399
|
+
exactly as before; its refusal now names the form as well as the
|
|
400
|
+
command that lists the names.
|
|
401
|
+
|
|
402
|
+
### Patch Changes
|
|
403
|
+
|
|
404
|
+
- 9878745: `bundle push` that refuses leaves nothing behind in the project
|
|
405
|
+
|
|
406
|
+
The environment check now stands in front of the packing step, not only in
|
|
407
|
+
front of the upload. Packing writes: a suite that has no `package-lock.json`
|
|
408
|
+
yet gets one, in your repository, next to your scenarios. So a push refused
|
|
409
|
+
for naming an environment the box does not have still ended with a file that
|
|
410
|
+
was not there before the command — the refusal cost something after all, and
|
|
411
|
+
the next `git status` showed it.
|
|
412
|
+
|
|
413
|
+
The order is now: the flags, the box address, the token, the environment, and
|
|
414
|
+
only then the suite is packed. Nothing that can be refused happens after the
|
|
415
|
+
first write.
|
|
416
|
+
|
|
417
|
+
What was already right stays: `--dry-run` still packs and still prints its
|
|
418
|
+
bundle id, without asking for a box. The refusals about a missing address and
|
|
419
|
+
a missing token answer in the same words and with the same exit codes as
|
|
420
|
+
before — they simply answer sooner, before the lockfile is touched. A push
|
|
421
|
+
that goes through writes the lockfile exactly as it always did.
|
|
422
|
+
|
|
423
|
+
- 8cc5b20: `bundle push --run` checks the environment name before uploading, and names the environments itself
|
|
424
|
+
|
|
425
|
+
Pushing with an environment name the box does not have uploaded the whole
|
|
426
|
+
bundle first, and only then came back with a refusal — so the last thing
|
|
427
|
+
on screen was "uploaded", the wasted upload looked like success, and the
|
|
428
|
+
refusal read like a problem with the box. The advice that followed was to
|
|
429
|
+
ask the operator which environments exist; on a rented box the customer
|
|
430
|
+
is the operator.
|
|
431
|
+
|
|
432
|
+
The name is now checked against the box's own list first, with the read
|
|
433
|
+
token `box envs` uses. An unknown name is refused before anything is
|
|
434
|
+
uploaded, and the refusal names what the box does serve:
|
|
435
|
+
|
|
436
|
+
✗ no environment named "staging" on https://larch.box.unotest.com
|
|
437
|
+
— nothing was uploaded.
|
|
438
|
+
it serves acme/test, acme/prod — push into one of them with
|
|
439
|
+
--env test
|
|
440
|
+
|
|
441
|
+
Both spellings the box itself gives are accepted, `acme/test` and the
|
|
442
|
+
bare `test`. Where the list cannot be had — no read token, a box that did
|
|
443
|
+
not answer, an empty list, which does not prove there are none — the push
|
|
444
|
+
goes ahead exactly as before and the box decides. Refusals that still
|
|
445
|
+
come from the box now point at `box envs` instead of an operator.
|
|
446
|
+
|
|
447
|
+
- 8a9c112: A fresh project can start a remote run: `init` writes a collection, and the refusal says what a box runs
|
|
448
|
+
|
|
449
|
+
A box runs a collection or a schedule; the local runner takes a scenario by
|
|
450
|
+
name. Both are true, and nothing said they differ — so a project created by
|
|
451
|
+
`init`, which wrote neither a collection nor a schedule, could not start a
|
|
452
|
+
remote run at all. The command the box's own screen offers came back with
|
|
453
|
+
"nothing to run", and the refusal named the state without naming the
|
|
454
|
+
difference or anything to try.
|
|
455
|
+
|
|
456
|
+
`init` now writes `unotest/e2e/_collections/examples.yaml`, naming the
|
|
457
|
+
examples it already creates. They hit the public playground, so the first
|
|
458
|
+
remote run passes before the box can reach the customer's own application:
|
|
459
|
+
|
|
460
|
+
npx @unotest/web bundle push --run --env <environment> --collection examples
|
|
461
|
+
|
|
462
|
+
A run asked for without a collection is still refused — that has not
|
|
463
|
+
changed — but the refusal now says that a box runs a collection or a
|
|
464
|
+
schedule rather than a scenario by name, and what to pass instead.
|
|
465
|
+
|
|
466
|
+
- 3f9bcb3: The box's screens fit a phone: the header wraps instead of running off the side
|
|
467
|
+
|
|
468
|
+
Four things sit in the header of every guard page — the box's own address,
|
|
469
|
+
who is looking, the link to the other half of the guard, and the way out. On
|
|
470
|
+
a screen 320 px wide they did not fit on one line, and what did not fit was
|
|
471
|
+
pushed past the right edge, taking the page's width with it. The whole screen
|
|
472
|
+
then scrolled sideways, and the control left outside was the one that signs a
|
|
473
|
+
viewer out: a box could not be left from a phone.
|
|
474
|
+
|
|
475
|
+
The header now wraps. On a narrow screen it takes a second row, and every
|
|
476
|
+
control stays reachable; a long box address or a long directory name wraps
|
|
477
|
+
with it instead of widening the row. Nothing changes on a wide screen — the
|
|
478
|
+
header is one row of the same height it always was.
|
|
479
|
+
|
|
480
|
+
- 7131b87: New box keeps the environment you named, so a box can be rented again
|
|
481
|
+
|
|
482
|
+
Renting a box from the cabinet was impossible for anyone who used the
|
|
483
|
+
Environment field the way it asks to be used. Typing `prod` and pressing Enter
|
|
484
|
+
turned the name into a tag, as it should; submitting the form then answered
|
|
485
|
+
"Name what runs at this address — prod, staging, pr-42." and came back with
|
|
486
|
+
the field empty and the tag gone. The form was refusing the very name it had
|
|
487
|
+
just been given, and there was no way through it: the only path to a first box
|
|
488
|
+
through the cabinet was closed.
|
|
489
|
+
|
|
490
|
+
The tag the browser makes now travels under the name the server reads
|
|
491
|
+
environments back from. It had been travelling under the name of the text box
|
|
492
|
+
it was typed in, so a row with one tag arrived with an address and no names at
|
|
493
|
+
all, and the refusal about names was the server honestly reporting what it
|
|
494
|
+
received.
|
|
495
|
+
|
|
496
|
+
Two things beside the fix, so this class of failure is harder to repeat. The
|
|
497
|
+
name a tag posts under is stated once, in the form, and the browser reads it
|
|
498
|
+
from there rather than spelling it out a second time. And the reader on the
|
|
499
|
+
server now accepts either half of the field repeated — two tags on one address
|
|
500
|
+
have always arrived as a repeated field, and a reader that expects a single
|
|
501
|
+
value drops a repeated one whole rather than in part.
|
|
502
|
+
|
|
503
|
+
A form submitted with scripting off is unchanged: the typed name still posts on
|
|
504
|
+
its own, one name per submit.
|
|
505
|
+
|
|
506
|
+
- e1db36f: `picker off` says how long the withdrawal takes, as one number
|
|
507
|
+
|
|
508
|
+
Switching the Picker off — a box's with `box picker off`, a project's with
|
|
509
|
+
`picker off` — withdraws its token through a revocation list that two
|
|
510
|
+
services read on their own schedules: the cloud rewrites the list, the
|
|
511
|
+
grounding service re-reads it. The command used to say only that the token
|
|
512
|
+
stops working "once the list is refreshed", because the CLI knew neither
|
|
513
|
+
interval and half a figure read as the whole would have been wrong.
|
|
514
|
+
|
|
515
|
+
The cloud now adds both halves and hands the sum over, and the command
|
|
516
|
+
says it:
|
|
517
|
+
|
|
518
|
+
✓ The Picker token for this box is revoked — the grounder stops
|
|
519
|
+
accepting it within about 2 minutes, once it refreshes its
|
|
520
|
+
revocation list
|
|
521
|
+
|
|
522
|
+
The dashboard's own notices after switching off say the same figure. When
|
|
523
|
+
the cloud cannot name it — the grounding service has not answered yet —
|
|
524
|
+
the sentence stays as it was, with no number: a default would be a
|
|
525
|
+
promise nothing keeps.
|
|
526
|
+
|
|
527
|
+
- 36d1dba: `bundle push --run --env` now names an environment the box will accept, and refuses one it cannot.
|
|
528
|
+
|
|
529
|
+
A box lists its environments as `<project>/<environment>`, and that is the
|
|
530
|
+
form its viewer API and every `box` subcommand take. The door that orders
|
|
531
|
+
runs takes the bare environment instead and fills the project in from the
|
|
532
|
+
token that pushed. The pre-upload check accepted both forms but carried
|
|
533
|
+
whichever was typed straight through, and a refusal advised the listed one
|
|
534
|
+
— so a push that followed our own advice was refused by the box after the
|
|
535
|
+
bundle had already been uploaded.
|
|
536
|
+
|
|
537
|
+
Both forms are still accepted, with one exception named below. Whichever is
|
|
538
|
+
given, the run is now ordered under the name the box runs, and a refusal
|
|
539
|
+
advises that same name.
|
|
540
|
+
|
|
541
|
+
On a box that serves several projects, a listed name is translated only
|
|
542
|
+
while the bare name it becomes belongs to one of them. Where two projects
|
|
543
|
+
share an environment name, the run would be ordered inside whichever
|
|
544
|
+
project your token belongs to — not necessarily the one you named — so the
|
|
545
|
+
push is refused before the upload instead, and the refusal names both:
|
|
546
|
+
|
|
547
|
+
✗ "beta/prod" cannot be ordered by that name on
|
|
548
|
+
https://larch.box.unotest.com — nothing was uploaded.
|
|
549
|
+
acme/prod and beta/prod are both called "prod", and a run is
|
|
550
|
+
ordered by the bare name inside the project your token belongs
|
|
551
|
+
to — push with --env prod if that project is yours
|
|
552
|
+
|
|
553
|
+
A bare name several projects share is left as it was typed: that is the
|
|
554
|
+
form the box expects, and the token is what says which project it means.
|
|
555
|
+
|
|
556
|
+
None of this reaches a box that has not named an environment yet. A box
|
|
557
|
+
with nothing to list answers with an empty list, which does not prove it
|
|
558
|
+
serves nothing, so the push goes ahead and the box decides — exactly as
|
|
559
|
+
before. Until a suite has been pushed to it once, a wrong name is still
|
|
560
|
+
answered by the box after the upload, not by this check.
|
|
561
|
+
|
|
562
|
+
- 5193108: The New box dialog closes again after the form has refused something
|
|
563
|
+
|
|
564
|
+
A dialog in the dashboard could be opened two ways and closed only one. The
|
|
565
|
+
Create box button opens it with a link to `#new-box`, and the × and the veil
|
|
566
|
+
close it by dropping that target again. A form the server refused comes back
|
|
567
|
+
open too — the refusal has to be visible — but that copy is opened by a class
|
|
568
|
+
in the served markup, and a link to `#` cannot take a class away. So anyone
|
|
569
|
+
who filled the form in, got a refusal, and reached for the × found the dialog
|
|
570
|
+
nailed open until they reloaded the page.
|
|
571
|
+
|
|
572
|
+
Closing now undoes whatever opened it. The dialog the button opened still
|
|
573
|
+
closes on the spot, with no request. The dialog a refusal opened closes by
|
|
574
|
+
going back to the boxes page — which has no refusal to keep it open — and the
|
|
575
|
+
× and the veil both carry that address. No script is involved in either case:
|
|
576
|
+
the dashboard is where boxes are bought, and it works with scripting off.
|
|
577
|
+
|
|
578
|
+
Closing after a refusal drops what was typed. That is the intended reading of
|
|
579
|
+
closing a form, and a draft that reappeared at the next click would be the
|
|
580
|
+
bigger surprise.
|
|
581
|
+
|
|
582
|
+
- @unotest/viewer@0.40.0
|
|
583
|
+
- @unotest/core@0.40.0
|
|
584
|
+
- @unotest/dsl@0.40.0
|
|
585
|
+
- @unotest/grounder-client@0.40.0
|
|
586
|
+
- @unotest/protocol@0.40.0
|
|
587
|
+
|
|
3
588
|
## [0.39.0] - 2026-09-18
|
|
4
589
|
|
|
5
590
|
### Minor Changes
|
|
@@ -66,7 +651,7 @@ picker on` fetches this project's own access and writes the three grounder
|
|
|
66
651
|
|
|
67
652
|
That screen named one command:
|
|
68
653
|
|
|
69
|
-
|
|
654
|
+
npx @unotest/web bundle push --run --env ENVIRONMENT
|
|
70
655
|
|
|
71
656
|
In a project that has just been created, the command refuses with `no box
|
|
72
657
|
address` before it does anything: the project holds neither the box's address
|
|
@@ -75,8 +660,8 @@ address` before it does anything: the project holds neither the box's address
|
|
|
75
660
|
|
|
76
661
|
The screen now names both steps, in the order they have to happen:
|
|
77
662
|
|
|
78
|
-
|
|
79
|
-
|
|
663
|
+
npx @unotest/web box access larch
|
|
664
|
+
npx @unotest/web bundle push --run --env ENVIRONMENT
|
|
80
665
|
|
|
81
666
|
The box's own name is filled in when its licence carries one. `box access`
|
|
82
667
|
with no name refuses, and its refusal offers to create a box — to somebody who
|
package/README.md
CHANGED
|
@@ -171,7 +171,10 @@ has no price of its own — and the answer defaults to no. On a box you already
|
|
|
171
171
|
this box's, which is left alone and reported. `off` also revokes the
|
|
172
172
|
box's Picker token, so a copy of it on another machine stops working
|
|
173
173
|
too: the grounder refuses it once it has re-read the operator's
|
|
174
|
-
revocation list
|
|
174
|
+
revocation list, and `off` says how long that takes — "within about 2
|
|
175
|
+
minutes" — whenever the cloud knows both halves of the wait. Where it
|
|
176
|
+
does not, the sentence stays true and carries no figure rather than
|
|
177
|
+
half of one. Either way the charge follows at the
|
|
175
178
|
next hourly tick, because a started hour is charged at the rate it
|
|
176
179
|
started with. `box status` says which state a box is in, and — while the
|
|
177
180
|
Picker is on — which models the cloud grounds with: `Our GPU` or
|
|
@@ -250,13 +253,18 @@ run reads:
|
|
|
250
253
|
|
|
251
254
|
```sh
|
|
252
255
|
npx @unotest/web env push dev # .env + .env.dev + .secrets + .secrets.dev
|
|
256
|
+
npx @unotest/web env set dev REGION eu-west
|
|
253
257
|
printf '%s' "$KEY" | npx @unotest/web env set dev API_KEY --secret
|
|
254
258
|
```
|
|
255
259
|
|
|
256
260
|
`.env*` become the environment's variables, `.secrets*` its secrets,
|
|
257
|
-
`APP_BASE_URL` its target; `UNOTEST_*` stay home. A value never
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
`APP_BASE_URL` its target; `UNOTEST_*` stay home. A secret's value never
|
|
262
|
+
prints; `env push --dry-run` shows what the other values resolved to. The
|
|
263
|
+
box and the token come from the same places `bundle push` reads them
|
|
264
|
+
from — flags, the environment, then `unotest/.env` and
|
|
265
|
+
`unotest/.secrets` — and the token must be minted with `--values` on the
|
|
266
|
+
box. Admins also see and change them on the box's admin page. Manual: [An
|
|
267
|
+
environment's values on a
|
|
260
268
|
box](https://docs.unotest.com/box/environments-and-values/).
|
|
261
269
|
|
|
262
270
|
A run on the box happens inside the box's own container: nothing on your
|
|
@@ -306,6 +314,16 @@ file; `--box` and `--token` win over both.
|
|
|
306
314
|
The token is never read from `unotest/.env` — that file travels inside a
|
|
307
315
|
pushed bundle, and `.secrets` does not.
|
|
308
316
|
|
|
317
|
+
`unotest/.secrets` is created owner-only (`0600`), so the tokens in it are
|
|
318
|
+
not readable by other users of a shared machine. A file left wider by an
|
|
319
|
+
earlier version is narrowed the next time a command writes to it, and the
|
|
320
|
+
command says so once. Editing a secret in the viewer's variables panel
|
|
321
|
+
keeps the same width. `unotest/.env` is configuration, not secrets, and
|
|
322
|
+
keeps whatever width your project gave it.
|
|
323
|
+
|
|
324
|
+
On Windows there are no POSIX permission bits, so nothing is narrowed there
|
|
325
|
+
and no width is promised: protect the file with the tools your system has.
|
|
326
|
+
|
|
309
327
|
Keeping it in the file is what lets your agent read runs: an MCP server is
|
|
310
328
|
started by your editor, so a token that only exists in your shell is a
|
|
311
329
|
token the agent never sees. A token added while the server is running is
|
|
@@ -317,7 +335,8 @@ hand you mint it for yourself on the box's guard, where it is shown once
|
|
|
317
335
|
and can be revoked. Either way it is read-only whatever your role on the
|
|
318
336
|
box — it cannot start a run or change a value. `UNOTEST_BOX_TOKEN` is the
|
|
319
337
|
project's push credential and cannot read runs. `box envs` lists the
|
|
320
|
-
environments
|
|
338
|
+
environments the box serves — an empty list means the box has none yet,
|
|
339
|
+
not that the token is too weak to see them.
|
|
321
340
|
|
|
322
341
|
## 5. Watch it run — the viewer
|
|
323
342
|
|