Compare commits
2
Commits
e6a18c92aa
...
dbdc416e77
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbdc416e77 | ||
|
|
1cfd69e72d |
@@ -277,7 +277,7 @@ provide:
|
|||||||
step's exit status, saying on stderr that it did and why. Every step of
|
step's exit status, saying on stderr that it did and why. Every step of
|
||||||
`make build` runs through it; `make build-debug` runs none of them through it.
|
`make build` runs through it; `make build-debug` runs none of them through it.
|
||||||
A step that succeeds removes nothing, and a removal that cannot be completed
|
A step that succeeds removes nothing, and a removal that cannot be completed
|
||||||
is reported as loudly as one that was
|
is reported as loudly as one that was.
|
||||||
- `script/test-verify-build` — exercise every failure mode of
|
- `script/test-verify-build` — exercise every failure mode of
|
||||||
`script/verify-build` against a fixture tree in a temp dir, asserting the exit
|
`script/verify-build` against a fixture tree in a temp dir, asserting the exit
|
||||||
status and the message of each, assert the state of `dist/` on disk after a
|
status and the message of each, assert the state of `dist/` on disk after a
|
||||||
@@ -1838,6 +1838,9 @@ view would leave a wallet one click from deletion.
|
|||||||
try again." on the error line, nothing deleted
|
try again." on the error line, nothing deleted
|
||||||
- "I have lost my password" → **DeleteWalletLostPassword**
|
- "I have lost my password" → **DeleteWalletLostPassword**
|
||||||
- "Back" → previous screen (Settings)
|
- "Back" → previous screen (Settings)
|
||||||
|
- Settings gear → **Settings**, whose "Back" never lands back on this
|
||||||
|
screen: leaving drops the wallet the screen was showing, so it also takes
|
||||||
|
the screen off the Back stack
|
||||||
|
|
||||||
#### DeleteWalletLostPassword (`delete-wallet-lost-password`)
|
#### DeleteWalletLostPassword (`delete-wallet-lost-password`)
|
||||||
|
|
||||||
@@ -1876,6 +1879,9 @@ view would leave a wallet one click from deletion.
|
|||||||
selection comes back with it. The two delete screens are siblings rather
|
selection comes back with it. The two delete screens are siblings rather
|
||||||
than parent and child: nothing is pushed on the way here, so both have
|
than parent and child: nothing is pushed on the way here, so both have
|
||||||
Settings as their Back target.
|
Settings as their Back target.
|
||||||
|
- Settings gear → **Settings**, whose "Back" never lands back on this
|
||||||
|
screen: leaving drops the wallet the screen was showing, so it also takes
|
||||||
|
the screen off the Back stack
|
||||||
- **Deliberately not password-gated.** A password in front of _discarding_ a
|
- **Deliberately not password-gated.** A password in front of _discarding_ a
|
||||||
secret protects nobody: an attacker at the popup who wants the wallet gone can
|
secret protects nobody: an attacker at the popup who wants the wallet gone can
|
||||||
uninstall the extension, so the only person such a gate stops is the owner who
|
uninstall the extension, so the only person such a gate stops is the owner who
|
||||||
|
|||||||
@@ -45,6 +45,29 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-07: `script/discard-dist-on-failure` returns the failed step's own
|
||||||
|
exit status even when it cannot write its message, to a closed stderr or to a
|
||||||
|
pipe nobody reads any more
|
||||||
|
([#342](https://git.eeqj.de/sneak/AutistMask/issues/342)); it used to return 2
|
||||||
|
or 141 instead. An interrupt while a step runs now removes nothing and says
|
||||||
|
nothing whichever shell `/bin/sh` is, even when the step catches it and exits
|
||||||
|
with a status of its own, as `script/check-censored` does: the wrapper exits
|
||||||
|
with 130 once the step has ended. Under bash such a step used to have `dist/`
|
||||||
|
removed. A failed `check-censored --require-dist` still removes `dist/` like
|
||||||
|
any other step, because a `dist/` not cleared of the name `RULES.md` bars must
|
||||||
|
not ship. The header states both. `script/test-verify-build` runs the wrapper
|
||||||
|
with stderr closed and with stderr a pipe nobody reads, and interrupts it
|
||||||
|
under dash and under bash.
|
||||||
|
|
||||||
|
- 2026-10-06: Back from Settings no longer lands on the delete wallet or
|
||||||
|
lost-password screen after either was left by the settings gear
|
||||||
|
([#480](https://git.eeqj.de/sneak/AutistMask/issues/480)), the defect
|
||||||
|
[#461](https://git.eeqj.de/sneak/AutistMask/issues/461) fixed for the two
|
||||||
|
secret screens. Leaving drops the screen's wallet selection, so its leave
|
||||||
|
handler now also takes it off the Back stack, as a reopened popup already
|
||||||
|
does. `tests/deleteWalletLostPassword.test.js` drives the gear and then Back
|
||||||
|
on both screens.
|
||||||
|
|
||||||
- 2026-10-06: `script/bootstrap` no longer reports success while node cannot
|
- 2026-10-06: `script/bootstrap` no longer reports success while node cannot
|
||||||
find a package listed in `dependencies` or `devDependencies` of `package.json`
|
find a package listed in `dependencies` or `devDependencies` of `package.json`
|
||||||
([#263](https://git.eeqj.de/sneak/AutistMask/issues/263)). After the install
|
([#263](https://git.eeqj.de/sneak/AutistMask/issues/263)). After the install
|
||||||
|
|||||||
@@ -3,22 +3,21 @@
|
|||||||
# step fails, remove dist/ before returning its exit status. Our own extension
|
# step fails, remove dist/ before returning its exit status. Our own extension
|
||||||
# to scripts-to-rule-them-all, wrapped around every step of make build.
|
# to scripts-to-rule-them-all, wrapped around every step of make build.
|
||||||
#
|
#
|
||||||
# Why: with AUTISTMASK_DEBUG=1 exported in the calling shell, make build
|
# Why: with AUTISTMASK_DEBUG=1 exported, make build compiles a debug bundle and
|
||||||
# compiles a debug bundle and then fails on it in script/verify-build — but the
|
# then fails on it in script/verify-build, after the bundle is written. It is
|
||||||
# bundle is already written. It is loadable, and every wallet it creates gets
|
# loadable, and every wallet it creates gets the publicly committed test
|
||||||
# the publicly committed test recovery phrase from src/shared/constants.js. A
|
# recovery phrase from src/shared/constants.js, so a failed build must not leave
|
||||||
# failed release build that leaves that behind is a smaller version of the trap
|
# it behind. The removal is never silent: it says on stderr that dist/ is gone
|
||||||
# the verifier exists to close, and "the failure was loud" only works on an
|
# and why.
|
||||||
# operator who does not load dist/chrome/ anyway. Removing the artifact does not
|
|
||||||
# depend on that.
|
|
||||||
#
|
#
|
||||||
# Two things this deliberately does not do. It does not wrap make build-debug: a
|
# A failed check-censored --require-dist removes dist/ like any other step: a
|
||||||
# debug build that failed is not a mistakable artifact, and its output is the
|
# dist/ not cleared of the name RULES.md bars must not ship either. A step that
|
||||||
# evidence of what went wrong. And it never removes anything on a step that
|
# succeeds removes nothing. An interrupt (Ctrl-C) while a step runs removes
|
||||||
# SUCCEEDS, including the final check-censored --require-dist pass.
|
# nothing and says nothing, even when the step catches it and exits with a
|
||||||
#
|
# status of its own: the wrapper exits with 130 once the step has ended. An
|
||||||
# The removal is never silent: it says dist/ is gone and why, on stderr, above
|
# interrupt is not a build failure, and whoever interrupted the build knows it
|
||||||
# the build's own failure.
|
# did not finish. make build-debug is not wrapped: a debug build that failed is
|
||||||
|
# not a mistakable artifact, and its output is the evidence of what went wrong.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
@@ -66,12 +65,20 @@ main() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# An interrupt is not a build failure: once the step has ended, exit
|
||||||
|
# without removing anything, even if the step caught the interrupt and
|
||||||
|
# exited with a status of its own. bash as /bin/sh would otherwise carry on.
|
||||||
|
trap 'exit 130' INT
|
||||||
|
|
||||||
_status=0
|
_status=0
|
||||||
"$@" || _status=$?
|
"$@" || _status=$?
|
||||||
|
|
||||||
[ "$_status" -ne 0 ] || return 0
|
[ "$_status" -ne 0 ] || return 0
|
||||||
|
|
||||||
discard_dist
|
# A message that cannot be written, to a closed stderr or to a pipe nobody
|
||||||
|
# reads any more, must not replace the step's status.
|
||||||
|
trap '' PIPE
|
||||||
|
discard_dist || true
|
||||||
exit "$_status"
|
exit "$_status"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -915,6 +915,76 @@ run_cases() {
|
|||||||
discard_case "the wrapper given no command removes nothing" \
|
discard_case "the wrapper given no command removes nothing" \
|
||||||
c_control 1 kept "no command given" ""
|
c_control 1 kept "no command given" ""
|
||||||
|
|
||||||
|
# With stderr closed the wrapper cannot write its message, and must still
|
||||||
|
# remove dist/ and return the step's own status.
|
||||||
|
build_fixture
|
||||||
|
_status=0
|
||||||
|
(cd "$FIXTURE" &&
|
||||||
|
"$FIXTURE/script/discard-dist-on-failure" sh -c 'exit 6' 2>&-) ||
|
||||||
|
_status=$?
|
||||||
|
if [ "$_status" -eq 6 ] && [ ! -e "$FIXTURE/dist" ]; then
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
echo " ok: a failed step's status survives a closed stderr"
|
||||||
|
else
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
echo " FAIL: a failed step's status survives a closed stderr"
|
||||||
|
echo " exit status $_status, wanted 6, and dist/ must be gone"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The same with stderr a pipe nobody reads any more, where the write would
|
||||||
|
# kill the wrapper with SIGPIPE. The FIFO's only reader opens it, exits and
|
||||||
|
# is waited for before the wrapper runs, so the pipe never has a reader.
|
||||||
|
build_fixture
|
||||||
|
mkfifo "$WORK/stderr-fifo"
|
||||||
|
_status=0
|
||||||
|
(
|
||||||
|
: <"$WORK/stderr-fifo" &
|
||||||
|
exec 3>"$WORK/stderr-fifo"
|
||||||
|
wait "$!"
|
||||||
|
cd "$FIXTURE" &&
|
||||||
|
"$FIXTURE/script/discard-dist-on-failure" sh -c 'exit 6' 2>&3
|
||||||
|
) || _status=$?
|
||||||
|
if [ "$_status" -eq 6 ] && [ ! -e "$FIXTURE/dist" ]; then
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
echo " ok: a failed step's status survives a pipe nobody reads"
|
||||||
|
else
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
echo " FAIL: a failed step's status survives a pipe nobody reads"
|
||||||
|
echo " exit status $_status, wanted 6, and dist/ must be gone"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# An interrupt while a step runs removes nothing and says nothing, even
|
||||||
|
# when the step catches it and exits with a status of its own, as
|
||||||
|
# script/check-censored does. The step interrupts the wrapper and then
|
||||||
|
# itself, as Ctrl-C interrupts every process of the build at once. Run
|
||||||
|
# under dash and under bash, which /bin/sh may each be: bash carries on
|
||||||
|
# after such a step unless the wrapper stops it.
|
||||||
|
for _shell in dash bash; do
|
||||||
|
_name="an interrupt under $_shell removes nothing"
|
||||||
|
if ! command -v "$_shell" >/dev/null 2>&1; then
|
||||||
|
SKIPPED=$((SKIPPED + 1))
|
||||||
|
SKIPPED_NAMES="$SKIPPED_NAMES## - $_name ($_shell not found)$NEWLINE"
|
||||||
|
echo " SKIP ($_shell not found): $_name"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
build_fixture
|
||||||
|
_status=0
|
||||||
|
_out="$(cd "$FIXTURE" && "$_shell" \
|
||||||
|
"$FIXTURE/script/discard-dist-on-failure" \
|
||||||
|
sh -c 'trap "exit 4" INT; kill -INT "$PPID" $$; exit 5' 2>&1)" ||
|
||||||
|
_status=$?
|
||||||
|
if [ "$_status" -eq 130 ] && [ -z "$_out" ] &&
|
||||||
|
[ -f "$FIXTURE/dist/chrome/src/popup/index.js" ]; then
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
echo " ok: $_name"
|
||||||
|
else
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
echo " FAIL: $_name"
|
||||||
|
echo " exit status $_status, wanted 130; dist/ must be intact" \
|
||||||
|
"and nothing said. Output: $_out"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
check_makefile_wiring
|
check_makefile_wiring
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ function confirmKey(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Drop the password from the DOM and the wallet selection from the
|
// Drop the password from the DOM and the wallet selection from the
|
||||||
// closure. Registered as the view-leave handler as well as run on entry,
|
// closure. Run by the view-leave handler as well as on entry, so the typed
|
||||||
// so the typed password does not sit in the hidden view after the user
|
// password does not sit in the hidden view after the user navigates away
|
||||||
// navigates away by any route, including the Settings gear.
|
// by any route, including the Settings gear.
|
||||||
function clear() {
|
function clear() {
|
||||||
deleteWalletIndex = null;
|
deleteWalletIndex = null;
|
||||||
$("delete-wallet-password").value = "";
|
$("delete-wallet-password").value = "";
|
||||||
@@ -147,8 +147,25 @@ async function finishDelete(walletIdx) {
|
|||||||
function init(_ctx) {
|
function init(_ctx) {
|
||||||
ctx = _ctx;
|
ctx = _ctx;
|
||||||
|
|
||||||
onViewLeave("delete-wallet-confirm", clear);
|
// Leaving drops the wallet selection, so each screen also comes off the
|
||||||
onViewLeave("delete-wallet-lost-password", clearLostPassword);
|
// Back stack, where the settings gear has just put it: Back from
|
||||||
|
// Settings must not land on a screen whose button can only answer "No
|
||||||
|
// wallet selected for deletion." A reopened popup drops them from the
|
||||||
|
// stack the same way (https://git.eeqj.de/sneak/AutistMask/issues/480).
|
||||||
|
onViewLeave("delete-wallet-confirm", () => {
|
||||||
|
clear();
|
||||||
|
const stack = state.viewStack;
|
||||||
|
if (stack[stack.length - 1] === "delete-wallet-confirm") {
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onViewLeave("delete-wallet-lost-password", () => {
|
||||||
|
clearLostPassword();
|
||||||
|
const stack = state.viewStack;
|
||||||
|
if (stack[stack.length - 1] === "delete-wallet-lost-password") {
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// No wipe here: goBack() routes through showView(), which runs the
|
// No wipe here: goBack() routes through showView(), which runs the
|
||||||
// leave hook.
|
// leave hook.
|
||||||
|
|||||||
@@ -615,3 +615,44 @@ describe("the password route's confirm button", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/480: leaving either delete
|
||||||
|
// screen drops its wallet selection, so Back onto one showed a screen whose
|
||||||
|
// button could only answer "No wallet selected for deletion."
|
||||||
|
describe("Back from Settings after leaving by the settings gear", () => {
|
||||||
|
test("does not land on the delete screen", () => {
|
||||||
|
const { helpers, deleteWallet, state } = load();
|
||||||
|
deleteWallet.show(1);
|
||||||
|
// The settings gear: push the current view, then show Settings.
|
||||||
|
helpers.pushCurrentView();
|
||||||
|
helpers.showView("settings");
|
||||||
|
|
||||||
|
expect(state.viewStack).toEqual(["main", "settings"]);
|
||||||
|
helpers.goBack();
|
||||||
|
expect(state.currentView).not.toBe("delete-wallet-confirm");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not land on the lost-password screen", async () => {
|
||||||
|
const { helpers, deleteWallet, state } = load();
|
||||||
|
await openLostPassword(deleteWallet, 1);
|
||||||
|
// The settings gear: push the current view, then show Settings.
|
||||||
|
helpers.pushCurrentView();
|
||||||
|
helpers.showView("settings");
|
||||||
|
|
||||||
|
expect(state.viewStack).toEqual(["main", "settings"]);
|
||||||
|
helpers.goBack();
|
||||||
|
expect(state.currentView).not.toBe(VIEW);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The lost-password screen's own Back is "Back returns to the delete
|
||||||
|
// screen with its wallet still chosen", above.
|
||||||
|
test("the delete screen's own Back leaves the rest of the stack alone", async () => {
|
||||||
|
const { deleteWallet, state } = load();
|
||||||
|
deleteWallet.show(1);
|
||||||
|
|
||||||
|
await click("btn-delete-wallet-back");
|
||||||
|
|
||||||
|
expect(state.currentView).toBe("settings");
|
||||||
|
expect(state.viewStack).toEqual(["main"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user