grr-cli 1.0.0

Google tools from the terminal, at maximum performance: 401 methods across 14 Google APIs in one generated command tree, zero-config, over HTTP/3
Documentation
---
import BaseLayout from '../layouts/BaseLayout.astro';
import changelog from '../data/changelog.json';

const REPO_URL = 'https://github.com/debanjanbasu/grr-cli';

// Internal links go through BASE_URL so they survive a change of `base` in
// astro.config.mjs instead of silently pointing at the production origin.
const base = import.meta.env.BASE_URL;

const { releases, generatedAt } = changelog;
const newest = releases[0];

// One flat list of everything, so a single <details> can summarise the whole
// history and each release stays independently linkable.
const totalChanges = releases.reduce(
  (total, release) => total + release.sections.reduce((n, s) => n + s.items.length, 0),
  0,
);

const dateFormatter = new Intl.DateTimeFormat('en-GB', {
  day: 'numeric',
  month: 'short',
  year: 'numeric',
  timeZone: 'UTC',
});

const formatDate = (iso: string | null) => (iso ? dateFormatter.format(new Date(`${iso}T00:00:00Z`)) : '');
---

<BaseLayout
  title="Changelog"
  description="Every release of grr, newest first: added commands, fixed bugs, performance work and breaking changes, generated from the commit history."
>
  <section class="section changelog-head pb-0">
    <div class="container">
      <span class="eyebrow">release history</span>
      <h1 class="mt-2 mb-3 text-[clamp(26px,3.2vw,42px)] leading-[1.12]">Changelog</h1>
      <p class="changelog-lede m-0 max-w-[62ch] text-sm text-base-content/70">
        Generated from the commit history and the release tags, so it cannot drift from what actually
        shipped. {releases.length} releases, {totalChanges} recorded changes.
      </p>
      <p class="changelog-meta mt-2 text-xs text-(--quiet)">
        {
          newest.unreleased ? (
            <>
              Unreleased work is on <a href={`${REPO_URL}/commits/main`}>main</a>. The newest tag is{' '}
              {releases.find((release) => !release.unreleased)?.version ?? 'none yet'}.
            </>
          ) : (
            <>
              Newest release: {newest.version}, {formatDate(newest.date)}.
            </>
          )
        }
        <a href={`${REPO_URL}/releases`}>GitHub releases</a> carry the binaries and checksums.
      </p>
    </div>
  </section>

  <section class="section changelog-body">
    <div class="container">
      <nav class="changelog-jump mb-8 flex flex-wrap gap-1.5" aria-label="Jump to a release" data-pagefind-ignore>
        {
          releases.map((release) => (
            <a class="badge badge-outline badge-sm" href={release.unreleased ? '#unreleased' : `#v${release.version.replace(/\./g, '-')}`}>
              {release.unreleased ? 'Unreleased' : release.version}
            </a>
          ))
        }
      </nav>

      <ul class="timeline timeline-vertical timeline-compact">
        {
          releases.map((release) => (
            <li>
              <div class="timeline-start timeline-box w-fit" id={release.unreleased ? 'unreleased' : `v${release.version.replace(/\./g, '-')}`}>
                <div class="flex items-center gap-2">
                  <h2 class="m-0 text-base font-normal">{release.unreleased ? 'Unreleased' : release.version}</h2>
                  {release.unreleased && <span class="badge badge-secondary badge-sm">in progress</span>}
                </div>
                {release.date && <time class="block text-xs text-(--muted)" datetime={release.date}>{formatDate(release.date)}</time>}
              </div>
              <hr />
              <div class="timeline-end timeline-box min-w-0 max-w-[72ch]">
                {release.sections.length === 0 && <p class="changelog-empty m-0 text-xs text-(--quiet)">No user-visible changes yet.</p>}
                {release.sections.map((section) => (
                  <section class={`changelog-section${section.key === 'breaking' ? ' is-breaking' : ''}`}>
                    <h3 class={`m-0 mb-1.5 text-sm ${section.key === 'breaking' ? 'text-(--accent-text)' : 'text-base-content/80'}`}>{section.heading}</h3>
                    <ul class="m-0 mb-2 list-none space-y-1 p-0">
                      {section.items.map((item) => (
                        <li class="text-[13px] text-base-content/70">
                          {item.scope && <span class="changelog-scope badge badge-ghost badge-sm mr-1.5">{item.scope}</span>}
                          <span>{item.description}</span>
                          {item.note && <span class="changelog-note block text-xs text-(--quiet)">{item.note}</span>}
                        </li>
                      ))}
                    </ul>
                  </section>
                ))}
              </div>
            </li>
          ))
        }
      </ul>

      <footer class="changelog-foot mt-10 border-t border-base-300 pt-6 text-xs text-(--quiet)">
        <p class="m-0">
          Generated <time datetime={generatedAt}>{new Date(generatedAt).toISOString().slice(0, 10)}</time>
          by <code>scripts/generate-changelog.mjs</code> and refreshed automatically.
        </p>
        <p class="m-0 mt-1">
          Looking for setup instructions instead? <a class="link link-hover" href={`${base}docs/getting-started/`}>Start here</a>, or see
          <a class="link link-hover" href={`${base}docs/`}>all documentation</a>.
        </p>
      </footer>
    </div>
  </section>
</BaseLayout>