docs: add feature coverage pages
This commit is contained in:
parent
8b78a94c76
commit
e322aad2e9
1
Makefile
1
Makefile
@ -74,6 +74,7 @@ docs-site: docs-commands
|
||||
@node scripts/build-docs-site.mjs
|
||||
|
||||
docs-check: docs-site
|
||||
@node scripts/check-docs-coverage.mjs
|
||||
|
||||
tools:
|
||||
@mkdir -p $(TOOLS_DIR)
|
||||
|
||||
@ -8,6 +8,8 @@ Keep, and related agent workflows.
|
||||
|
||||
- Install and authenticate from the repository
|
||||
[README](https://github.com/steipete/gogcli#readme).
|
||||
- Read [Install and Runtime Packages](install.md) when installing from
|
||||
Homebrew, Docker, GitHub releases, Windows ZIPs, or source.
|
||||
- Read [Auth Clients](auth-clients.md) when setting up OAuth clients, service
|
||||
accounts, or Workspace domain-wide delegation.
|
||||
- Read [Command Guards and Baked Safety Profiles](safety-profiles.md) when
|
||||
@ -20,6 +22,27 @@ Keep, and related agent workflows.
|
||||
- Open the [Command Index](commands/README.md) for generated docs for every CLI
|
||||
command.
|
||||
|
||||
## Feature Pages
|
||||
|
||||
- [Install and Runtime Packages](install.md)
|
||||
- [Auth Clients](auth-clients.md)
|
||||
- [Command Guards and Baked Safety Profiles](safety-profiles.md)
|
||||
- [Raw API Dumps](raw-api.md)
|
||||
- [Raw API Sensitive Field Audit](raw-audit.md)
|
||||
- [Gmail Workflows](gmail-workflows.md)
|
||||
- [Gmail watch](watch.md)
|
||||
- [Email Tracking](email-tracking.md)
|
||||
- [Drive Audits](drive-audits.md)
|
||||
- [Contacts Dedupe Preview](contacts-dedupe.md)
|
||||
- [Contacts JSON Update](contacts-json-update.md)
|
||||
- [Google Docs Editing](docs-editing.md)
|
||||
- [Sheets Tables](sheets-tables.md)
|
||||
- [Sheets Formatting](sheets-formatting.md)
|
||||
- [Slides from Markdown](slides-markdown.md)
|
||||
- [Slides Template Replacement](slides-template-replacement.md)
|
||||
- [Backups](backup.md)
|
||||
- [Date and Time Input Formats](dates.md)
|
||||
|
||||
## Common Paths
|
||||
|
||||
```bash
|
||||
@ -40,6 +63,9 @@ commands, flags, aliases, arguments, or help text, run:
|
||||
make docs-commands
|
||||
```
|
||||
|
||||
`make docs-check` verifies that every schema command has a generated page and
|
||||
that required feature pages are present and linked from this overview.
|
||||
|
||||
Then build the GitHub Pages site locally:
|
||||
|
||||
```bash
|
||||
|
||||
68
docs/contacts-dedupe.md
Normal file
68
docs/contacts-dedupe.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Contacts Dedupe Preview
|
||||
|
||||
read_when:
|
||||
- Finding duplicate Google Contacts.
|
||||
- Reviewing or changing `gog contacts dedupe`.
|
||||
|
||||
`gog contacts dedupe` finds likely duplicate personal contacts and prints a
|
||||
merge plan. It is preview-only: it does not merge, update, or delete contacts.
|
||||
|
||||
## Command Page
|
||||
|
||||
- [`gog contacts dedupe`](commands/gog-contacts-dedupe.md)
|
||||
|
||||
## Basic Use
|
||||
|
||||
```bash
|
||||
gog contacts dedupe
|
||||
gog contacts dedupe --json
|
||||
gog contacts dedupe --max 500 --json
|
||||
```
|
||||
|
||||
Default matching uses normalized email and phone values:
|
||||
|
||||
```bash
|
||||
gog contacts dedupe --match email,phone
|
||||
```
|
||||
|
||||
Name matching is opt-in because it can produce false positives:
|
||||
|
||||
```bash
|
||||
gog contacts dedupe --match email,phone,name
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
The command groups contacts that share a matching key. JSON output includes:
|
||||
|
||||
- `scanned`: number of contacts examined
|
||||
- `groups`: likely duplicate groups
|
||||
- `primary`: the contact gog would keep first in a hypothetical merge plan
|
||||
- `merged`: merged emails/phones for preview
|
||||
- `matched_on`: duplicate email/phone/name keys that caused the group
|
||||
- `members`: all contacts in the group
|
||||
|
||||
## Safety
|
||||
|
||||
`contacts dedupe` is read-only. There is no apply flag.
|
||||
|
||||
Use `--dry-run` in automation anyway when you want a uniform safety habit across
|
||||
commands:
|
||||
|
||||
```bash
|
||||
gog contacts dedupe --dry-run --json
|
||||
```
|
||||
|
||||
Use `--fail-empty` in scheduled checks when "no duplicates" should be reported
|
||||
as a distinct exit code:
|
||||
|
||||
```bash
|
||||
gog contacts dedupe --fail-empty
|
||||
```
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Raw API Dumps](raw-api.md)
|
||||
- [Generated Contacts command pages](commands/gog-contacts.md)
|
||||
- [`gog contacts export`](commands/gog-contacts-export.md)
|
||||
- [`gog contacts raw`](commands/gog-contacts-raw.md)
|
||||
94
docs/docs-editing.md
Normal file
94
docs/docs-editing.md
Normal file
@ -0,0 +1,94 @@
|
||||
# Google Docs Editing
|
||||
|
||||
read_when:
|
||||
- Editing Google Docs content, tabs, formatting, comments, or raw Docs output.
|
||||
- Reviewing Docs write, format, find-replace, or tab commands.
|
||||
|
||||
Docs commands cover document creation, export, content writes, find/replace,
|
||||
comments, tabs, formatting, and raw API inspection.
|
||||
|
||||
## Write Markdown
|
||||
|
||||
Append Markdown and convert it to Google Docs formatting:
|
||||
|
||||
```bash
|
||||
gog docs write <docId> --append --markdown --text '## Status'
|
||||
```
|
||||
|
||||
Replace the document body with Markdown from a file:
|
||||
|
||||
```bash
|
||||
gog docs write <docId> --replace --markdown --content-file README.md
|
||||
```
|
||||
|
||||
Command pages:
|
||||
|
||||
- [`gog docs write`](commands/gog-docs-write.md)
|
||||
- [`gog docs export`](commands/gog-docs-export.md)
|
||||
- [`gog docs cat`](commands/gog-docs-cat.md)
|
||||
|
||||
## Format Text
|
||||
|
||||
Apply text or paragraph formatting:
|
||||
|
||||
```bash
|
||||
gog docs format <docId> --match Status --bold --font-size 18
|
||||
gog docs format <docId> --match "Action item" --text-color '#b00020'
|
||||
gog docs format <docId> --match Heading --alignment center --line-spacing 120
|
||||
```
|
||||
|
||||
Use `--match-all` when every occurrence should be formatted.
|
||||
|
||||
Command page:
|
||||
|
||||
- [`gog docs format`](commands/gog-docs-format.md)
|
||||
|
||||
## Tabs
|
||||
|
||||
Manage Google Docs tabs:
|
||||
|
||||
```bash
|
||||
gog docs list-tabs <docId>
|
||||
gog docs add-tab <docId> --title "Notes"
|
||||
gog docs rename-tab <docId> <tabId> "Archive"
|
||||
gog docs delete-tab <docId> <tabId> --force
|
||||
```
|
||||
|
||||
Tab-aware commands accept `--tab` by title or ID:
|
||||
|
||||
```bash
|
||||
gog docs write <docId> --append --tab "Notes" --text "Follow-up"
|
||||
gog docs find-replace <docId> old new --tab "Notes" --dry-run
|
||||
```
|
||||
|
||||
Command pages:
|
||||
|
||||
- [`gog docs list-tabs`](commands/gog-docs-list-tabs.md)
|
||||
- [`gog docs add-tab`](commands/gog-docs-add-tab.md)
|
||||
- [`gog docs rename-tab`](commands/gog-docs-rename-tab.md)
|
||||
- [`gog docs delete-tab`](commands/gog-docs-delete-tab.md)
|
||||
|
||||
## Find and Replace
|
||||
|
||||
```bash
|
||||
gog docs find-replace <docId> old new --dry-run
|
||||
gog docs find-replace <docId> old '' --first
|
||||
gog docs find-replace <docId> PLACEHOLDER --content-file replacement.md --format markdown
|
||||
```
|
||||
|
||||
`--dry-run` is read-only and reports match counts. Empty replacement strings are
|
||||
allowed and delete matches.
|
||||
|
||||
Command page:
|
||||
|
||||
- [`gog docs find-replace`](commands/gog-docs-find-replace.md)
|
||||
|
||||
## Raw Docs Output
|
||||
|
||||
Use raw output when a script needs the Google Docs API object:
|
||||
|
||||
```bash
|
||||
gog docs raw <docId> --pretty
|
||||
```
|
||||
|
||||
See [Raw API Dumps](raw-api.md) for lossless-output safety notes.
|
||||
75
docs/drive-audits.md
Normal file
75
docs/drive-audits.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Drive Audits
|
||||
|
||||
read_when:
|
||||
- Auditing Drive folder contents, size, or inventory without changing files.
|
||||
- Reviewing `drive tree`, `drive du`, or `drive inventory`.
|
||||
|
||||
Drive audit commands are read-only reporting helpers. They are meant for cleanup
|
||||
planning, migration review, and automation that needs stable JSON without
|
||||
writing back to Drive.
|
||||
|
||||
## Commands
|
||||
|
||||
- [`gog drive tree`](commands/gog-drive-tree.md)
|
||||
- [`gog drive du`](commands/gog-drive-du.md)
|
||||
- [`gog drive inventory`](commands/gog-drive-inventory.md)
|
||||
- [`gog drive ls`](commands/gog-drive-ls.md)
|
||||
- [`gog drive get`](commands/gog-drive-get.md)
|
||||
- [`gog drive raw`](commands/gog-drive-raw.md)
|
||||
|
||||
## Folder Tree
|
||||
|
||||
Print a readable folder tree:
|
||||
|
||||
```bash
|
||||
gog drive tree --parent <folderId> --depth 2
|
||||
```
|
||||
|
||||
Use JSON when another tool should consume the result:
|
||||
|
||||
```bash
|
||||
gog drive tree --parent <folderId> --depth 3 --json
|
||||
```
|
||||
|
||||
## Size Summary
|
||||
|
||||
Summarize folder sizes:
|
||||
|
||||
```bash
|
||||
gog drive du --parent <folderId> --max 20
|
||||
gog drive du --parent <folderId> --depth 2 --sort size --json
|
||||
```
|
||||
|
||||
`drive du` counts files under folders and sorts by `size`, `path`, or `files`.
|
||||
|
||||
## Inventory Export
|
||||
|
||||
Export a read-only item inventory:
|
||||
|
||||
```bash
|
||||
gog drive inventory --parent <folderId> --json
|
||||
gog drive inventory --parent <folderId> --max 0 --depth 0 --json > drive-inventory.json
|
||||
```
|
||||
|
||||
Use inventory output when you need a machine-readable list of Drive objects for
|
||||
review, diffing, or downstream cleanup scripts.
|
||||
|
||||
## Shared Drives
|
||||
|
||||
The audit commands include shared drives by default where the underlying Drive
|
||||
API supports it. Pass `--no-all-drives` to restrict a scan to My Drive:
|
||||
|
||||
```bash
|
||||
gog drive inventory --parent root --no-all-drives --json
|
||||
```
|
||||
|
||||
## Custom Fields
|
||||
|
||||
For object-level inspection, use `drive get --fields`:
|
||||
|
||||
```bash
|
||||
gog drive get <fileId> --fields 'id,name,mimeType,size,owners,emailAddress' --json
|
||||
```
|
||||
|
||||
Use [`gog drive raw`](commands/gog-drive-raw.md) when you need the raw Drive API
|
||||
object, with the sensitive-field behavior described in [Raw API Dumps](raw-api.md).
|
||||
88
docs/gmail-workflows.md
Normal file
88
docs/gmail-workflows.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Gmail Workflows
|
||||
|
||||
read_when:
|
||||
- Working with Gmail content, filters, watches, labels, or agent-safe reads.
|
||||
- Reviewing Gmail commands that cross from read-only into send or modify flows.
|
||||
|
||||
Gmail is one of gog's broadest surfaces. Use command-specific pages for exact
|
||||
flags, and use this page to choose the right workflow shape.
|
||||
|
||||
## Search and Read
|
||||
|
||||
```bash
|
||||
gog gmail search 'newer_than:7d' --max 10 --json
|
||||
gog gmail get <messageId> --json
|
||||
gog gmail thread get <threadId> --json
|
||||
```
|
||||
|
||||
For agents, logs, or issue reports, prefer sanitized content:
|
||||
|
||||
```bash
|
||||
gog gmail get <messageId> --sanitize-content --json
|
||||
gog gmail thread get <threadId> --sanitize-content --json
|
||||
```
|
||||
|
||||
`--sanitize-content` strips unsafe/raw payload details while keeping useful
|
||||
message text for automation.
|
||||
|
||||
## Filters
|
||||
|
||||
Export filters as Gmail WebUI-compatible XML:
|
||||
|
||||
```bash
|
||||
gog gmail settings filters export --out filters.xml
|
||||
```
|
||||
|
||||
Keep API JSON when a script needs the Gmail API shape:
|
||||
|
||||
```bash
|
||||
gog gmail settings filters export --format json --json
|
||||
```
|
||||
|
||||
Command pages:
|
||||
|
||||
- [`gog gmail settings filters export`](commands/gog-gmail-settings-filters-export.md)
|
||||
- [`gog gmail settings filters list`](commands/gog-gmail-settings-filters-list.md)
|
||||
- [`gog gmail settings filters create`](commands/gog-gmail-settings-filters-create.md)
|
||||
- [`gog gmail settings filters delete`](commands/gog-gmail-settings-filters-delete.md)
|
||||
|
||||
## Send Guardrails
|
||||
|
||||
Block send operations globally for one run:
|
||||
|
||||
```bash
|
||||
gog --gmail-no-send gmail send --to you@example.com --subject test --text body
|
||||
```
|
||||
|
||||
Or use the environment variable in agent shells:
|
||||
|
||||
```bash
|
||||
export GOG_GMAIL_NO_SEND=1
|
||||
```
|
||||
|
||||
For account-specific send blocking, use the no-send config commands:
|
||||
|
||||
- [`gog config no-send set`](commands/gog-config-no-send-set.md)
|
||||
- [`gog config no-send list`](commands/gog-config-no-send-list.md)
|
||||
- [`gog config no-send remove`](commands/gog-config-no-send-remove.md)
|
||||
|
||||
## Watches and Push
|
||||
|
||||
Gmail watch/PubSub workflows are documented in [Gmail watch](watch.md).
|
||||
|
||||
Key command pages:
|
||||
|
||||
- [`gog gmail watch start`](commands/gog-gmail-settings-watch-start.md)
|
||||
- [`gog gmail watch serve`](commands/gog-gmail-settings-watch-serve.md)
|
||||
- [`gog gmail watch renew`](commands/gog-gmail-settings-watch-renew.md)
|
||||
- [`gog gmail history`](commands/gog-gmail-history.md)
|
||||
|
||||
## Email Tracking
|
||||
|
||||
Open tracking is documented in [Email Tracking](email-tracking.md) and
|
||||
[Email Tracking Worker](email-tracking-worker.md).
|
||||
|
||||
## Raw Gmail
|
||||
|
||||
Use [`gog gmail raw`](commands/gog-gmail-raw.md) when you need the underlying
|
||||
Gmail API `Message` object. See [Raw API Dumps](raw-api.md) for safety notes.
|
||||
82
docs/install.md
Normal file
82
docs/install.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Install and Runtime Packages
|
||||
|
||||
read_when:
|
||||
- Updating release packages, Docker images, or install instructions.
|
||||
- Debugging version mismatches between source, Homebrew, and downloaded assets.
|
||||
|
||||
`gog` ships as a single binary. The visible version is injected at build time:
|
||||
release builds use the tag, while local builds use `git describe`.
|
||||
|
||||
## Homebrew
|
||||
|
||||
```bash
|
||||
brew install gogcli
|
||||
gog --version
|
||||
```
|
||||
|
||||
The Homebrew formula lives in `steipete/homebrew-tap` and installs the `gog`
|
||||
binary. Release verification should install or upgrade the tap formula and run:
|
||||
|
||||
```bash
|
||||
brew test steipete/tap/gogcli
|
||||
gog --version
|
||||
```
|
||||
|
||||
## GitHub Releases
|
||||
|
||||
Release assets are uploaded by GoReleaser:
|
||||
|
||||
- `gogcli_<version>_darwin_amd64.tar.gz`
|
||||
- `gogcli_<version>_darwin_arm64.tar.gz`
|
||||
- `gogcli_<version>_linux_amd64.tar.gz`
|
||||
- `gogcli_<version>_linux_arm64.tar.gz`
|
||||
- `gogcli_<version>_windows_amd64.zip`
|
||||
- `gogcli_<version>_windows_arm64.zip`
|
||||
- `checksums.txt`
|
||||
|
||||
Windows users download the matching ZIP, extract `gog.exe`, and add the
|
||||
directory to `PATH`.
|
||||
|
||||
## Docker
|
||||
|
||||
Release tags publish a GitHub Container Registry image:
|
||||
|
||||
```bash
|
||||
docker run --rm ghcr.io/steipete/gogcli:latest version
|
||||
docker run --rm ghcr.io/steipete/gogcli:v0.15.0 version
|
||||
```
|
||||
|
||||
Authenticated container runs should mount a persistent config directory and use
|
||||
the encrypted file keyring:
|
||||
|
||||
```bash
|
||||
docker volume create gogcli-config
|
||||
|
||||
docker run --rm -it \
|
||||
-e GOG_KEYRING_BACKEND=file \
|
||||
-e GOG_KEYRING_PASSWORD \
|
||||
-v gogcli-config:/home/gog/.config/gogcli \
|
||||
ghcr.io/steipete/gogcli:latest \
|
||||
auth add you@gmail.com --services gmail,calendar,drive
|
||||
```
|
||||
|
||||
Keep `GOG_KEYRING_PASSWORD` in the shell session or CI secret store. Do not bake
|
||||
it into images, scripts, or checked-in profiles.
|
||||
|
||||
## Source Builds
|
||||
|
||||
```bash
|
||||
git clone https://github.com/steipete/gogcli.git
|
||||
cd gogcli
|
||||
make
|
||||
./bin/gog --version
|
||||
```
|
||||
|
||||
Source builds require the Go version declared in `go.mod`.
|
||||
|
||||
## Related Command Pages
|
||||
|
||||
- [`gog version`](commands/gog-version.md)
|
||||
- [`gog auth keyring`](commands/gog-auth-keyring.md)
|
||||
- [`gog auth credentials`](commands/gog-auth-credentials.md)
|
||||
- [`gog auth add`](commands/gog-auth-add.md)
|
||||
64
docs/raw-api.md
Normal file
64
docs/raw-api.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Raw API Dumps
|
||||
|
||||
read_when:
|
||||
- Using `gog <service> raw` commands for lossless Google API JSON.
|
||||
- Passing Google API responses into scripts, debuggers, or LLM workflows.
|
||||
- Reviewing sensitive-field behavior for raw output.
|
||||
|
||||
Raw commands return the canonical Google API response shape instead of gog's
|
||||
normal curated table/JSON output. They are useful when a script needs a field
|
||||
that gog does not model yet, or when debugging an API object exactly as Google
|
||||
returns it.
|
||||
|
||||
## Commands
|
||||
|
||||
- [`gog calendar raw`](commands/gog-calendar-raw.md)
|
||||
- [`gog contacts raw`](commands/gog-contacts-raw.md)
|
||||
- [`gog docs raw`](commands/gog-docs-raw.md)
|
||||
- [`gog drive raw`](commands/gog-drive-raw.md)
|
||||
- [`gog forms raw`](commands/gog-forms-raw.md)
|
||||
- [`gog gmail raw`](commands/gog-gmail-raw.md)
|
||||
- [`gog people raw`](commands/gog-people-raw.md)
|
||||
- [`gog sheets raw`](commands/gog-sheets-raw.md)
|
||||
- [`gog slides raw`](commands/gog-slides-raw.md)
|
||||
- [`gog tasks raw`](commands/gog-tasks-raw.md)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
gog drive raw <fileId> --pretty
|
||||
gog docs raw <docId> --json > doc-api.json
|
||||
gog gmail raw <messageId> --format metadata --json
|
||||
gog sheets raw <spreadsheetId> --include-grid-data --json
|
||||
```
|
||||
|
||||
Use service-native field masks when available:
|
||||
|
||||
```bash
|
||||
gog drive raw <fileId> --fields 'id,name,mimeType,owners(emailAddress)' --json
|
||||
gog contacts raw people/c123 --person-fields names,emailAddresses,phoneNumbers --json
|
||||
```
|
||||
|
||||
## Safety Model
|
||||
|
||||
Raw output is intentionally less opinionated than normal gog output. It may
|
||||
include private document content, contact data, event attendees, Gmail payloads,
|
||||
or service-specific metadata.
|
||||
|
||||
Drive has the highest capability-URL risk. By default, `gog drive raw` redacts
|
||||
fields such as `thumbnailLink`, `webContentLink`, `exportLinks`, `resourceKey`,
|
||||
`properties`, `appProperties`, and embedded thumbnail bytes unless the user
|
||||
explicitly names fields via `--fields`.
|
||||
|
||||
Sheets warns when grid data or developer metadata could expose sensitive data,
|
||||
but keeps output lossless. Docs and Slides may include short-lived image URLs.
|
||||
|
||||
For the full sensitive-field review, read [Raw API Sensitive Field Audit](raw-audit.md).
|
||||
|
||||
## Automation Tips
|
||||
|
||||
- Prefer `--json` for scripts.
|
||||
- Prefer `--pretty` for humans.
|
||||
- Use narrow `--fields` or service-specific field masks whenever possible.
|
||||
- Do not pipe raw output into logs or LLMs unless you are comfortable with the
|
||||
object's full Google API payload.
|
||||
@ -8,10 +8,10 @@ const outDir = path.join(root, "dist", "docs-site");
|
||||
const repoEditBase = "https://github.com/steipete/gogcli/edit/main/docs";
|
||||
|
||||
const sections = [
|
||||
["Start", ["README.md", "auth-clients.md", "spec.md", "dates.md"]],
|
||||
["Start", ["README.md", "install.md", "auth-clients.md", "spec.md", "dates.md"]],
|
||||
["Commands", rels("commands")],
|
||||
["Gmail", ["gmail-autoreply.md", "watch.md", "email-tracking.md", "email-tracking-worker.md"]],
|
||||
["Workspace", ["backup.md", "sheets-tables.md", "contacts-json-update.md", "slides-markdown.md", "slides-template-replacement.md", "sedmat.md"]],
|
||||
["Gmail", ["gmail-workflows.md", "gmail-autoreply.md", "watch.md", "email-tracking.md", "email-tracking-worker.md"]],
|
||||
["Workspace", ["raw-api.md", "raw-audit.md", "drive-audits.md", "contacts-dedupe.md", "contacts-json-update.md", "docs-editing.md", "sheets-tables.md", "sheets-formatting.md", "slides-markdown.md", "slides-template-replacement.md", "backup.md", "sedmat.md"]],
|
||||
["Safety", ["safety-profiles.md", "RELEASING.md"]],
|
||||
];
|
||||
|
||||
|
||||
137
scripts/check-docs-coverage.mjs
Normal file
137
scripts/check-docs-coverage.mjs
Normal file
@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env node
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const root = process.cwd();
|
||||
const bin = process.env.GOG_BIN || path.join(root, "bin", "gog");
|
||||
const docsDir = path.join(root, "docs");
|
||||
const commandsDir = path.join(docsDir, "commands");
|
||||
|
||||
const requiredFeatureDocs = [
|
||||
"install.md",
|
||||
"auth-clients.md",
|
||||
"safety-profiles.md",
|
||||
"raw-api.md",
|
||||
"raw-audit.md",
|
||||
"gmail-workflows.md",
|
||||
"watch.md",
|
||||
"email-tracking.md",
|
||||
"drive-audits.md",
|
||||
"contacts-dedupe.md",
|
||||
"contacts-json-update.md",
|
||||
"docs-editing.md",
|
||||
"sheets-tables.md",
|
||||
"sheets-formatting.md",
|
||||
"slides-markdown.md",
|
||||
"slides-template-replacement.md",
|
||||
"backup.md",
|
||||
"dates.md",
|
||||
];
|
||||
|
||||
const schema = JSON.parse(execFileSync(bin, ["schema", "--json"], { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }));
|
||||
const commands = Array.from(walk(schema.command || {}));
|
||||
const seenSlugs = new Set();
|
||||
const missingCommandPages = [];
|
||||
|
||||
for (const command of commands) {
|
||||
const base = commandSlug(command);
|
||||
let slug = base;
|
||||
let suffix = 2;
|
||||
while (seenSlugs.has(slug)) {
|
||||
slug = `${base}-${suffix}`;
|
||||
suffix += 1;
|
||||
}
|
||||
seenSlugs.add(slug);
|
||||
|
||||
const page = path.join(commandsDir, `${slug}.md`);
|
||||
if (!fs.existsSync(page)) {
|
||||
missingCommandPages.push(path.relative(root, page));
|
||||
}
|
||||
}
|
||||
|
||||
const docsReadme = fs.readFileSync(path.join(docsDir, "README.md"), "utf8");
|
||||
const missingFeaturePages = [];
|
||||
const unlinkedFeaturePages = [];
|
||||
const brokenLinks = checkMarkdownLinks(docsDir);
|
||||
|
||||
for (const rel of requiredFeatureDocs) {
|
||||
const page = path.join(docsDir, rel);
|
||||
if (!fs.existsSync(page)) {
|
||||
missingFeaturePages.push(`docs/${rel}`);
|
||||
continue;
|
||||
}
|
||||
if (!docsReadme.includes(`(${rel})`)) {
|
||||
unlinkedFeaturePages.push(`docs/${rel}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingCommandPages.length || missingFeaturePages.length || unlinkedFeaturePages.length || brokenLinks.length) {
|
||||
for (const name of missingCommandPages) console.error(`missing command doc: ${name}`);
|
||||
for (const name of missingFeaturePages) console.error(`missing feature doc: ${name}`);
|
||||
for (const name of unlinkedFeaturePages) console.error(`feature doc not linked from docs/README.md: ${name}`);
|
||||
for (const item of brokenLinks) console.error(`broken docs link: ${item}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`docs coverage ok: ${commands.length} command pages, ${requiredFeatureDocs.length} feature pages`);
|
||||
|
||||
function* walk(command) {
|
||||
yield command;
|
||||
for (const child of command.subcommands || []) {
|
||||
yield* walk(child);
|
||||
}
|
||||
}
|
||||
|
||||
function canonicalTokens(commandPath) {
|
||||
return (commandPath || "")
|
||||
.split(/\s+/)
|
||||
.filter((part) => part && !(part.startsWith("(") && part.endsWith(")")));
|
||||
}
|
||||
|
||||
function canonicalPath(command) {
|
||||
return canonicalTokens(command.path || command.name || "").join(" ");
|
||||
}
|
||||
|
||||
function commandSlug(command) {
|
||||
const slug = canonicalPath(command)
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
return slug || "gog";
|
||||
}
|
||||
|
||||
function checkMarkdownLinks(dir) {
|
||||
const broken = [];
|
||||
for (const file of allMarkdown(dir)) {
|
||||
const markdown = fs.readFileSync(file, "utf8");
|
||||
const linkPattern = /!?\[[^\]]*\]\(([^)]+)\)/g;
|
||||
let match;
|
||||
while ((match = linkPattern.exec(markdown)) !== null) {
|
||||
const rawTarget = match[1].trim().replace(/^<|>$/g, "");
|
||||
if (!rawTarget || rawTarget.startsWith("#")) continue;
|
||||
if (/^[a-z][a-z0-9+.-]*:/i.test(rawTarget)) continue;
|
||||
|
||||
const targetWithoutTitle = rawTarget.split(/\s+["'][^"']*["']\s*$/)[0];
|
||||
const targetPath = targetWithoutTitle.split("#")[0];
|
||||
if (!targetPath) continue;
|
||||
if (/^(url|path|file)$/i.test(targetPath)) continue;
|
||||
|
||||
const resolved = path.resolve(path.dirname(file), targetPath);
|
||||
if (!fs.existsSync(resolved)) {
|
||||
broken.push(`${path.relative(root, file)} -> ${targetPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return broken;
|
||||
}
|
||||
|
||||
function allMarkdown(dir) {
|
||||
return fs
|
||||
.readdirSync(dir, { withFileTypes: true })
|
||||
.flatMap((entry) => {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) return allMarkdown(full);
|
||||
return entry.name.endsWith(".md") ? [full] : [];
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user