Skip to content

1.33.0 — Search Console, proven and reachable

Choose a tag to compare

@github-actions github-actions released this 24 Aug 17:27
· 59 commits to main since this release

Added

  • The app tells you an update exists, and offers to fetch it. It knew
    before — checkIfDue() has had a one-a-day guard for versions — but it was
    only called from the main view's .task, which runs once when the window
    appears. An app left open for a week checked once, in that week's first
    minute, and a release cut the next morning went unmentioned until somebody
    quit and came back. And when it did know, the only place that said so was a
    Settings pane somebody had to think to open.

    Now an hourly timer and didBecomeActive both funnel through the same
    day-old guard — the timer for an app left running, becoming active for the
    laptop that was shut overnight, since a timer neither fires while asleep nor
    keeps its schedule afterwards.

    A banner sits above the report rather than in front of it: a new version is
    worth mentioning and never worth interrupting a crawl for. Dismissing it
    dismisses that version for good, because a bar that returns every launch is a
    bar people learn to ignore.

    Downloading shows a real fraction where GitHub sent a length and an
    indeterminate spinner where it did not — a bar that sits at zero and jumps to
    full is worse than one that admits it cannot say. It unpacks with ditto,
    which is what wrote the archive and what keeps the bundle's signature intact.

    It stops at the drag, deliberately. Replacing a running bundle safely
    needs a helper process that outlives the app it is overwriting, which is
    Sparkle's whole job; and a Homebrew install has one correct answer that is not
    this one, since overwriting the bundle behind brew's back leaves its records
    describing a version that is no longer there. So a cask install gets the
    brew command run in Terminal where it can be watched, and everything else
    gets the file revealed in Finder.

  • A Search Console pane in the macOS app, and the whole Help row is now the
    click target rather than its chevron. DisclosureGroup only hit-tests its own
    triangle, which left a full-width row that looked clickable everywhere and
    answered in one corner — that reads as the app being broken, not as a small
    control.

    The pane takes the property and offers a sign-in, which runs the engine's own
    --search-console-login through the bundled Node. The token never passes
    through the app and is never displayed: a token on screen is a token in a
    screenshot. What comes back is the list of properties the account can read,
    each with a button to use it, because a token that can read nothing looks
    exactly like one that works until a run says the property was not found.

    The hosted Worker will not honour ?search-console= unless the runtime
    sets ALLOW_SEARCH_CONSOLE, and --serve sets it only because it binds to
    the loopback address. The credentials belong to whoever started the server, so
    a deployed Worker accepting a property name would hand a stranger somebody
    else's traffic data. Gated, shape-checked, and tested.

  • A Help pane in the macOS app. Written as the questions the app actually
    raises — why a crawl takes minutes, why the page count differs from the
    sitemap's, where the score is, why performance is blank until it is switched
    on, what leaves the machine — rather than a tour of the controls. A control
    that needs explaining is better renamed; a decision is what needs saying,
    and this app makes several that surprise people.

    Folded away rather than expanded: a wall of prose in a settings window is a
    wall nobody reads, and a list of questions is scannable.

  • --search-console-login. The three Search Console credentials were
    documented for a year and there was never a way to obtain the third, which is
    the actual reason --search-console had never run against the live API: not
    the code, the paperwork in front of it.

    Loopback OAuth, which is what Google calls the installed-app flow — a desktop
    client may redirect to any port on 127.0.0.1 without registering it, so this
    listens on an ephemeral one and the browser does the signing in. Read-only
    scope. The refresh token is written to ~/.config/seo-audit/.env at mode
    600 and never printed, because a token echoed to a terminal is a token in a
    scrollback buffer. It rewrites that one line and leaves the PageSpeed key
    alone, which is tested, because clobbering somebody's key to save a token
    would be a poor trade.

    Then it lists the properties the account can read. A token that can read
    nothing looks exactly like one that works, right up until an audit says the
    property was not found.

    Not a GitHub Action input, deliberately: it opens a browser, and a flag CI
    can accept and never satisfy is worse than no flag. src/options.mjs records
    the same answer for the macOS window.

    The parts that can be wrong quietly are separate exported functions with
    tests — the authorisation URL, the token exchange, the file rewrite — and the
    loopback flow itself is exercised end to end with only Google faked,
    including that a reply carrying the wrong state is refused and writes
    nothing.

Fixed

  • Search Console could never read its credentials from the dotfile. It had
    its own copy of the loader that psi.mjs uses, and its copy built the pattern
    with new RegExp and a template literal, where \\s survives as an escaped
    backslash rather than as whitespace. The regex compiled to
    /^\\s*GSC_CLIENT_ID\\s*=.../m — a literal backslash followed by s — so it
    could not match a line of a real .env. It never threw. Only environment
    variables ever worked.

    Nothing caught it because the only tests for that path injected credentials
    and used a fake API, which is exactly what "never run against the live API"
    hides. There is now one loader in config.mjs that both callers use, and it
    is tested against a dotfile with the whitespace a hand-edited file actually
    has — including a name that is a prefix of another, which the broken pattern
    would also have got wrong.