desktop coverage: every CLI command mapped to its window place (cli/lib/desktop-coverage.ts), docs/desktop-coverage.md generated from it, guard test fails on a gap or an unjustified not-for-desktop
This commit is contained in:
parent
a95aa6dd49
commit
dc6bb088a1
4 changed files with 371 additions and 0 deletions
138
cli/lib/desktop-coverage.ts
Normal file
138
cli/lib/desktop-coverage.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// Where every CLI command (cli/commands.ts COMMANDS + PLAIN) lives in the desktop app. One source for
|
||||
// tools/gen-desktop-coverage.ts (docs/desktop-coverage.md) and the guard test (tests/desktop-coverage.test.ts): a
|
||||
// command without an entry, or with a method the window never calls, fails the test.
|
||||
//
|
||||
// methods: serve.ts methods (`play.listing`, `owner.play_rollout`) or host commands of src-tauri (`host:accounts_list`).
|
||||
// owner: the owner-only parts of the command; the More tools tab lists them as terminal steps (OWNER_STEPS).
|
||||
|
||||
export type Coverage =
|
||||
| { where: string; methods: string[]; owner?: string }
|
||||
| { not: string };
|
||||
|
||||
/** The More tools tab (apps/desktop/src/screens/tabs/Tools.tsx): one typed form per method, built from its schema
|
||||
* (`system.methods`). Reads run; writes go through the dry run, the native dialog and the confirm token. */
|
||||
export const TOOLBOX: { id: string; methods: string[] }[] = [
|
||||
{ id: 'build', methods: ['play.build', 'play.upload', 'play.share', 'play.next_version_code', 'play.app_id', 'apple.build', 'apple.upload', 'apple.next_build',
|
||||
'apple.signing_status', 'apple.signing_ensure', 'apple.bundle', 'apple.testflight_info', 'apple.testflight_wait'] },
|
||||
{ id: 'adhoc', methods: ['apple.adhoc_devices', 'apple.adhoc_register', 'apple.adhoc_options', 'apple.adhoc_export'] },
|
||||
{ id: 'store', methods: ['apple.iap_review', 'apple.iap_review_plan', 'apple.creative_assets', 'apple.creative_assets_verify', 'apple.cpp_list', 'apple.cpp_create',
|
||||
'apple.cpp_upload', 'apple.events_list', 'apple.events_create', 'apple.events_upload', 'apple.preorder_status', 'catalog.pull', 'onboard', 'revenuecat.diff', 'prices.ppp'] },
|
||||
{ id: 'text', methods: ['whats.new', 'whats.new_languages', 'whats.new_check', 'whats.new_apply', 'listing.translate', 'listing.check', 'check.aso', 'aso.rank',
|
||||
'aso.competitors', 'aso.suggest'] },
|
||||
{ id: 'capture', methods: ['shots.capture', 'shots.record', 'shots.check', 'shots.fonts', 'shots.jpeg'] },
|
||||
{ id: 'quality', methods: ['health.report', 'triage', 'originality', 'privacy.manifest', 'ads.scan', 'web.init', 'web.check'] },
|
||||
{ id: 'ads', methods: ['ads.apple_status', 'ads.apple_campaigns', 'ads.apple_report'] },
|
||||
{ id: 'flows', methods: ['flow.list', 'flow.run', 'agent.install'] },
|
||||
];
|
||||
|
||||
/** Owner-only steps without a window button: the More tools tab shows each with its terminal command to copy. */
|
||||
export const OWNER_STEPS: { id: string; command: string }[] = [
|
||||
{ id: 'offer_codes', command: 'storebound apple offer-codes' },
|
||||
{ id: 'win_back', command: 'storebound apple win-back' },
|
||||
{ id: 'promoted', command: 'storebound apple promoted --enable' },
|
||||
{ id: 'preorder', command: 'storebound apple preorder enable --release-date YYYY-MM-DD' },
|
||||
{ id: 'cpp_submit', command: 'storebound apple cpp submit --name <page>' },
|
||||
{ id: 'events_submit', command: 'storebound apple events submit --name <event>' },
|
||||
{ id: 'tf_expire', command: 'storebound apple testflight expire --keep 5' },
|
||||
{ id: 'ads_change', command: 'storebound ads apple bid --campaign <id> --keyword <id> --bid 1.20' },
|
||||
{ id: 'rc_sync', command: 'storebound revenuecat sync' },
|
||||
{ id: 'shots_login', command: 'storebound shots login' },
|
||||
];
|
||||
|
||||
const T = 'More tools';
|
||||
export const DESKTOP_COVERAGE: Record<string, Coverage> = {
|
||||
// PLAIN
|
||||
'init': { where: 'App folders: Start an app folder', methods: ['host:project_init'] },
|
||||
'import': { where: 'App folders / first-run guide: Import from fastlane', methods: ['host:project_import_fastlane'] },
|
||||
'install': { where: `${T}: Install for an AI tool`, methods: ['agent.install'] },
|
||||
'mcp': { not: 'the stdio server for AI tools; the window itself is the GUI over the same commands' },
|
||||
'serve': { not: 'the JSON-RPC sidecar the window runs on; nothing to click' },
|
||||
'apps': { where: 'Accounts (store apps per account, key check) and App folders (list, add, remove)', methods: ['host:store_apps_refresh', 'host:account_check', 'host:projects_list', 'host:project_add', 'host:project_remove'] },
|
||||
'desktop': { not: 'opens or installs this window from a terminal' },
|
||||
'accounts': { where: 'Accounts screen (profiles shared with the CLI)', methods: ['host:accounts_list', 'host:account_add_play', 'host:account_add_apple', 'host:account_update'] },
|
||||
'doctor': { where: 'Dependencies screen', methods: ['system.tools', 'host:tools_info'] },
|
||||
'scan': { where: 'Checks tab: Scan, Fix', methods: ['scan', 'scan.write'] },
|
||||
'api': { not: 'raw API calls by operation id: every task has its own screen, and raw writes stay a terminal step the owner types' },
|
||||
'privacy-manifest': { where: `${T}: Quality`, methods: ['privacy.manifest'] },
|
||||
'ws': { where: 'Portfolio, studios and Settings (the workspace of this machine)', methods: ['host:workspace_get', 'host:portfolio_status'] },
|
||||
'release': { where: 'Portfolio release trains and the Releases tab', methods: ['host:portfolio_status', 'apple.status', 'play.status'] },
|
||||
'flow': { where: `${T}: Workflows (plans; owner steps never run)`, methods: ['flow.list', 'flow.run'] },
|
||||
'ads': { where: `${T}: Quality (AdAttributionKit / SKAdNetwork scan)`, methods: ['ads.scan'] },
|
||||
// COMMANDS
|
||||
'edit': { where: 'Editors on every tab (Save edits)', methods: ['project.editable', 'project.edit'] },
|
||||
'inspect': { where: 'App card, App info, Listings, Store art (offline reads)', methods: ['host:portfolio_status', 'project.declarations', 'project.listings', 'project.listing_text', 'project.icon', 'project.render'] },
|
||||
'check': { where: 'Checks tab', methods: ['check'] },
|
||||
'validate': { where: 'Checks tab', methods: ['validate'] },
|
||||
'report': { where: 'Checks tab: readiness report', methods: ['report'] },
|
||||
'analytics': { where: 'Growth tab: chart, snapshot, setup', methods: ['analytics.compare', 'analytics.snapshot', 'analytics.setup'] },
|
||||
'health': { where: 'Health tab; weekly report in More tools', methods: ['health', 'health.issue', 'health.report'] },
|
||||
'art': { where: 'Store art tab: live art, pull, replace', methods: ['art.live', 'art.thumb', 'art.pull', 'art.replace'] },
|
||||
'onboard': { where: `${T}: Store features`, methods: ['onboard'] },
|
||||
'catalog': { where: 'Catalog tab (parity); pull in More tools', methods: ['catalog.diff', 'catalog.pull'] },
|
||||
'reports': { where: 'Growth tab: sales and finance reports', methods: ['reports'] },
|
||||
'triage': { where: `${T}: Quality`, methods: ['triage'] },
|
||||
'aso': { where: 'Growth tab: ASO audit; rank, competitors, suggest in More tools', methods: ['aso.audit', 'aso.rank', 'aso.competitors', 'aso.suggest'] },
|
||||
'prices': { where: 'Prices tab (export, compare, apply = owner action); PPP sheet in More tools', methods: ['prices.current', 'prices.export', 'prices.diff', 'owner.prices_import', 'prices.ppp'] },
|
||||
'ads apple': { where: `${T}: Apple Ads (status, campaigns, report)`, methods: ['ads.apple_status', 'ads.apple_campaigns', 'ads.apple_report'], owner: 'bid, budget, pause' },
|
||||
'web': { where: `${T}: Quality (init, check)`, methods: ['web.init', 'web.check'] },
|
||||
'revenuecat': { where: `${T}: Store features (diff)`, methods: ['revenuecat.diff'], owner: 'sync' },
|
||||
'console plan': { where: 'App info: Console steps without an API', methods: ['console.plan'] },
|
||||
'console verify': { where: 'App info: Console steps, Check', methods: ['console.verify'] },
|
||||
'console fill': { where: 'App info: Fill in console', methods: ['console.fill'] },
|
||||
'reviews': { where: 'Reviews tab (list, draft, reply, approve; Send = owner action)', methods: ['reviews.list', 'reviews.draft', 'reviews.drafts', 'reviews.reply', 'reviews.approve', 'owner.reply_send'] },
|
||||
'originality': { where: `${T}: Quality`, methods: ['originality'] },
|
||||
'listing': { where: 'Listings tab (live texts, store edits); translate and check in More tools', methods: ['listing.live', 'listing.set', 'listing.translate', 'listing.check'] },
|
||||
'whats-new': { where: `${T}: Text and ASO`, methods: ['whats.new', 'whats.new_languages', 'whats.new_check', 'whats.new_apply'] },
|
||||
'play products': { where: 'Catalog tab', methods: ['play.products', 'owner.prices_play_products'] },
|
||||
'play subscriptions': { where: 'Catalog tab', methods: ['play.subscriptions', 'owner.prices_play_subscriptions'] },
|
||||
'play listing': { where: 'Listings tab (push, pull)', methods: ['play.listing', 'play.listing_pull'] },
|
||||
'play images': { where: 'Listings tab: Play graphics', methods: ['play.images'] },
|
||||
'play data-safety': { where: 'App info tab: Data safety upload', methods: ['play.data_safety'] },
|
||||
'play release': { where: 'Releases tab: Send to production (owner action); testing tracks via play upload in More tools', methods: ['owner.play_production', 'play.upload'] },
|
||||
'play build': { where: `${T}: Build and upload`, methods: ['play.build'] },
|
||||
'play upload': { where: `${T}: Build and upload`, methods: ['play.upload'] },
|
||||
'play status': { where: 'Releases tab: Google Play card', methods: ['play.status'] },
|
||||
'play rollout': { where: 'Releases tab: rollout (owner action)', methods: ['owner.play_rollout'] },
|
||||
'play share': { where: `${T}: Build and upload`, methods: ['play.share'] },
|
||||
'play app-id': { where: `${T}: Build and upload`, methods: ['play.app_id'] },
|
||||
'play next-version-code': { where: `${T}: Build and upload`, methods: ['play.next_version_code'] },
|
||||
'apple bundle': { where: `${T}: Build and upload`, methods: ['apple.bundle'] },
|
||||
'apple iap': { where: 'Catalog tab', methods: ['apple.iap'] },
|
||||
'apple subscriptions': { where: 'Catalog tab', methods: ['apple.subscriptions', 'owner.prices_apple_subscriptions'] },
|
||||
'apple iap-review': { where: `${T}: Store features`, methods: ['apple.iap_review'] },
|
||||
'apple iap-submit': { where: 'Releases tab: Submit in-app purchases (owner action); plan in More tools', methods: ['owner.apple_iap_submit', 'apple.iap_review_plan'] },
|
||||
'apple submit': { where: 'Releases tab: Submit for review (owner action)', methods: ['owner.apple_submit'] },
|
||||
'apple resubmit': { where: 'Releases tab: Resubmit (owner action)', methods: ['owner.apple_resubmit'] },
|
||||
'apple offer-codes': { where: `${T}: owner steps (terminal command)`, methods: [], owner: 'creating campaigns and codes' },
|
||||
'apple win-back': { where: `${T}: owner steps (terminal command)`, methods: [], owner: 'win-back offers go live on their start date' },
|
||||
'apple promoted': { where: `${T}: owner steps (terminal command)`, methods: [], owner: 'promoted purchases on the product page' },
|
||||
'apple preorder': { where: `${T}: Store features (status); enable / end as owner steps`, methods: ['apple.preorder_status'], owner: 'enable, end' },
|
||||
'apple cpp': { where: `${T}: Store features (list, create, upload)`, methods: ['apple.cpp_list', 'apple.cpp_create', 'apple.cpp_upload'], owner: 'submit, visible' },
|
||||
'apple events': { where: `${T}: Store features (list, create, upload)`, methods: ['apple.events_list', 'apple.events_create', 'apple.events_upload'], owner: 'submit' },
|
||||
'apple listing': { where: 'Listings tab (push, pull)', methods: ['apple.listing', 'apple.listing_pull'] },
|
||||
'apple screenshots': { where: 'Listings tab: App Store screenshots', methods: ['apple.screenshots'] },
|
||||
'apple appinfo': { where: 'App info tab: Push', methods: ['apple.appinfo'] },
|
||||
'apple review-details': { where: 'App info tab: App Review details', methods: ['apple.review_details'] },
|
||||
'apple status': { where: 'Releases tab: Send for review, App Store card', methods: ['apple.status'] },
|
||||
'apple creative-assets': { where: `${T}: Store features`, methods: ['apple.creative_assets', 'apple.creative_assets_verify'] },
|
||||
'apple previews': { where: 'Listings tab: App Store previews', methods: ['apple.previews'] },
|
||||
'apple release': { where: 'Releases tab: phased release (owner action)', methods: ['owner.ios_phased'] },
|
||||
'apple signing': { where: `${T}: Build and upload (status, ensure)`, methods: ['apple.signing_status', 'apple.signing_ensure'] },
|
||||
'apple build': { where: `${T}: Build and upload`, methods: ['apple.build'] },
|
||||
'apple upload': { where: `${T}: Build and upload`, methods: ['apple.upload'] },
|
||||
'apple adhoc': { where: `${T}: Ad-hoc devices (devices, register, options, export)`, methods: ['apple.adhoc_devices', 'apple.adhoc_register', 'apple.adhoc_options', 'apple.adhoc_export'] },
|
||||
'apple next-build': { where: `${T}: Build and upload`, methods: ['apple.next_build'] },
|
||||
'apple encryption': { where: 'App info tab: export compliance', methods: ['apple.encryption'] },
|
||||
'apple ppo': { where: 'Growth tab: PPO tests (start / stop = owner actions)', methods: ['apple.ppo_status', 'apple.ppo_create', 'apple.ppo_upload', 'owner.ppo_start', 'owner.ppo_stop'] },
|
||||
'apple testflight': { where: 'TestFlight tab (status, groups, What to Test, distribute, testers = owner action); info and wait in More tools', methods: ['apple.testflight_status', 'apple.testflight_groups', 'apple.testflight_what_to_test', 'apple.testflight_distribute', 'owner.testflight_testers', 'apple.testflight_info', 'apple.testflight_wait'], owner: 'expire' },
|
||||
'icons': { where: 'Store art tab: icons', methods: ['icons'] },
|
||||
'shots capture': { where: `${T}: Captures`, methods: ['shots.capture'] },
|
||||
'shots login': { where: `${T}: owner steps (terminal command)`, methods: [], owner: 'signing in to a demo account by hand in a visible browser' },
|
||||
'shots fonts': { where: `${T}: Captures`, methods: ['shots.fonts'] },
|
||||
'shots render': { where: 'Store art tab: Render', methods: ['shots.render'] },
|
||||
'shots jpeg': { where: `${T}: Captures`, methods: ['shots.jpeg'] },
|
||||
'shots check': { where: `${T}: Captures`, methods: ['shots.check'] },
|
||||
'shots record': { where: `${T}: Captures`, methods: ['shots.record'] },
|
||||
'shots video': { where: 'Store art tab: previews', methods: ['shots.video'] },
|
||||
'shots gallery': { where: 'Store art tab: gallery, review page', methods: ['shots.gallery', 'project.renders'] },
|
||||
};
|
||||
103
docs/desktop-coverage.md
Normal file
103
docs/desktop-coverage.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# Desktop coverage
|
||||
|
||||
Generated by `bun tools/gen-desktop-coverage.ts` from `cli/lib/desktop-coverage.ts`; do not edit by hand.
|
||||
Every CLI command (cli/commands.ts) and the desktop place that runs it. "covered" means each method listed exists
|
||||
(serve.ts, or a host command `host:`) and the window calls it; tests/desktop-coverage.test.ts fails on a gap.
|
||||
Owner-only parts are owner actions in the window or owner steps with their terminal command (More tools).
|
||||
|
||||
**92 commands:** 88 covered, 0 gaps, 4 not for the desktop.
|
||||
|
||||
| command | status | where in the window | methods | owner step |
|
||||
|---|---|---|---|---|
|
||||
| `init` | covered | App folders: Start an app folder | `host:project_init` | |
|
||||
| `import` | covered | App folders / first-run guide: Import from fastlane | `host:project_import_fastlane` | |
|
||||
| `install` | covered | More tools: Install for an AI tool | `agent.install` | |
|
||||
| `mcp` | not-for-desktop | the stdio server for AI tools; the window itself is the GUI over the same commands | | |
|
||||
| `serve` | not-for-desktop | the JSON-RPC sidecar the window runs on; nothing to click | | |
|
||||
| `apps` | covered | Accounts (store apps per account, key check) and App folders (list, add, remove) | `host:store_apps_refresh`, `host:account_check`, `host:projects_list`, `host:project_add`, `host:project_remove` | |
|
||||
| `desktop` | not-for-desktop | opens or installs this window from a terminal | | |
|
||||
| `accounts` | covered | Accounts screen (profiles shared with the CLI) | `host:accounts_list`, `host:account_add_play`, `host:account_add_apple`, `host:account_update` | |
|
||||
| `doctor` | covered | Dependencies screen | `system.tools`, `host:tools_info` | |
|
||||
| `scan` | covered | Checks tab: Scan, Fix | `scan`, `scan.write` | |
|
||||
| `api` | not-for-desktop | raw API calls by operation id: every task has its own screen, and raw writes stay a terminal step the owner types | | |
|
||||
| `privacy-manifest` | covered | More tools: Quality | `privacy.manifest` | |
|
||||
| `ws` | covered | Portfolio, studios and Settings (the workspace of this machine) | `host:workspace_get`, `host:portfolio_status` | |
|
||||
| `release` | covered | Portfolio release trains and the Releases tab | `host:portfolio_status`, `apple.status`, `play.status` | |
|
||||
| `flow` | covered | More tools: Workflows (plans; owner steps never run) | `flow.list`, `flow.run` | |
|
||||
| `ads` | covered | More tools: Quality (AdAttributionKit / SKAdNetwork scan) | `ads.scan` | |
|
||||
| `edit` | covered | Editors on every tab (Save edits) | `project.editable`, `project.edit` | |
|
||||
| `inspect` | covered | App card, App info, Listings, Store art (offline reads) | `host:portfolio_status`, `project.declarations`, `project.listings`, `project.listing_text`, `project.icon`, `project.render` | |
|
||||
| `check` | covered | Checks tab | `check` | |
|
||||
| `validate` | covered | Checks tab | `validate` | |
|
||||
| `report` | covered | Checks tab: readiness report | `report` | |
|
||||
| `analytics` | covered | Growth tab: chart, snapshot, setup | `analytics.compare`, `analytics.snapshot`, `analytics.setup` | |
|
||||
| `health` | covered | Health tab; weekly report in More tools | `health`, `health.issue`, `health.report` | |
|
||||
| `art` | covered | Store art tab: live art, pull, replace | `art.live`, `art.thumb`, `art.pull`, `art.replace` | |
|
||||
| `onboard` | covered | More tools: Store features | `onboard` | |
|
||||
| `catalog` | covered | Catalog tab (parity); pull in More tools | `catalog.diff`, `catalog.pull` | |
|
||||
| `reports` | covered | Growth tab: sales and finance reports | `reports` | |
|
||||
| `triage` | covered | More tools: Quality | `triage` | |
|
||||
| `aso` | covered | Growth tab: ASO audit; rank, competitors, suggest in More tools | `aso.audit`, `aso.rank`, `aso.competitors`, `aso.suggest` | |
|
||||
| `prices` | covered | Prices tab (export, compare, apply = owner action); PPP sheet in More tools | `prices.current`, `prices.export`, `prices.diff`, `owner.prices_import`, `prices.ppp` | |
|
||||
| `ads apple` | covered | More tools: Apple Ads (status, campaigns, report) | `ads.apple_status`, `ads.apple_campaigns`, `ads.apple_report` | bid, budget, pause |
|
||||
| `web` | covered | More tools: Quality (init, check) | `web.init`, `web.check` | |
|
||||
| `revenuecat` | covered | More tools: Store features (diff) | `revenuecat.diff` | sync |
|
||||
| `console plan` | covered | App info: Console steps without an API | `console.plan` | |
|
||||
| `console verify` | covered | App info: Console steps, Check | `console.verify` | |
|
||||
| `console fill` | covered | App info: Fill in console | `console.fill` | |
|
||||
| `reviews` | covered | Reviews tab (list, draft, reply, approve; Send = owner action) | `reviews.list`, `reviews.draft`, `reviews.drafts`, `reviews.reply`, `reviews.approve`, `owner.reply_send` | |
|
||||
| `originality` | covered | More tools: Quality | `originality` | |
|
||||
| `listing` | covered | Listings tab (live texts, store edits); translate and check in More tools | `listing.live`, `listing.set`, `listing.translate`, `listing.check` | |
|
||||
| `whats-new` | covered | More tools: Text and ASO | `whats.new`, `whats.new_languages`, `whats.new_check`, `whats.new_apply` | |
|
||||
| `play products` | covered | Catalog tab | `play.products`, `owner.prices_play_products` | |
|
||||
| `play subscriptions` | covered | Catalog tab | `play.subscriptions`, `owner.prices_play_subscriptions` | |
|
||||
| `play listing` | covered | Listings tab (push, pull) | `play.listing`, `play.listing_pull` | |
|
||||
| `play images` | covered | Listings tab: Play graphics | `play.images` | |
|
||||
| `play data-safety` | covered | App info tab: Data safety upload | `play.data_safety` | |
|
||||
| `play release` | covered | Releases tab: Send to production (owner action); testing tracks via play upload in More tools | `owner.play_production`, `play.upload` | |
|
||||
| `play build` | covered | More tools: Build and upload | `play.build` | |
|
||||
| `play upload` | covered | More tools: Build and upload | `play.upload` | |
|
||||
| `play status` | covered | Releases tab: Google Play card | `play.status` | |
|
||||
| `play rollout` | covered | Releases tab: rollout (owner action) | `owner.play_rollout` | |
|
||||
| `play share` | covered | More tools: Build and upload | `play.share` | |
|
||||
| `play app-id` | covered | More tools: Build and upload | `play.app_id` | |
|
||||
| `play next-version-code` | covered | More tools: Build and upload | `play.next_version_code` | |
|
||||
| `apple bundle` | covered | More tools: Build and upload | `apple.bundle` | |
|
||||
| `apple iap` | covered | Catalog tab | `apple.iap` | |
|
||||
| `apple subscriptions` | covered | Catalog tab | `apple.subscriptions`, `owner.prices_apple_subscriptions` | |
|
||||
| `apple iap-review` | covered | More tools: Store features | `apple.iap_review` | |
|
||||
| `apple iap-submit` | covered | Releases tab: Submit in-app purchases (owner action); plan in More tools | `owner.apple_iap_submit`, `apple.iap_review_plan` | |
|
||||
| `apple submit` | covered | Releases tab: Submit for review (owner action) | `owner.apple_submit` | |
|
||||
| `apple resubmit` | covered | Releases tab: Resubmit (owner action) | `owner.apple_resubmit` | |
|
||||
| `apple offer-codes` | covered | More tools: owner steps (terminal command) | | creating campaigns and codes |
|
||||
| `apple win-back` | covered | More tools: owner steps (terminal command) | | win-back offers go live on their start date |
|
||||
| `apple promoted` | covered | More tools: owner steps (terminal command) | | promoted purchases on the product page |
|
||||
| `apple preorder` | covered | More tools: Store features (status); enable / end as owner steps | `apple.preorder_status` | enable, end |
|
||||
| `apple cpp` | covered | More tools: Store features (list, create, upload) | `apple.cpp_list`, `apple.cpp_create`, `apple.cpp_upload` | submit, visible |
|
||||
| `apple events` | covered | More tools: Store features (list, create, upload) | `apple.events_list`, `apple.events_create`, `apple.events_upload` | submit |
|
||||
| `apple listing` | covered | Listings tab (push, pull) | `apple.listing`, `apple.listing_pull` | |
|
||||
| `apple screenshots` | covered | Listings tab: App Store screenshots | `apple.screenshots` | |
|
||||
| `apple appinfo` | covered | App info tab: Push | `apple.appinfo` | |
|
||||
| `apple review-details` | covered | App info tab: App Review details | `apple.review_details` | |
|
||||
| `apple status` | covered | Releases tab: Send for review, App Store card | `apple.status` | |
|
||||
| `apple creative-assets` | covered | More tools: Store features | `apple.creative_assets`, `apple.creative_assets_verify` | |
|
||||
| `apple previews` | covered | Listings tab: App Store previews | `apple.previews` | |
|
||||
| `apple release` | covered | Releases tab: phased release (owner action) | `owner.ios_phased` | |
|
||||
| `apple signing` | covered | More tools: Build and upload (status, ensure) | `apple.signing_status`, `apple.signing_ensure` | |
|
||||
| `apple build` | covered | More tools: Build and upload | `apple.build` | |
|
||||
| `apple upload` | covered | More tools: Build and upload | `apple.upload` | |
|
||||
| `apple adhoc` | covered | More tools: Ad-hoc devices (devices, register, options, export) | `apple.adhoc_devices`, `apple.adhoc_register`, `apple.adhoc_options`, `apple.adhoc_export` | |
|
||||
| `apple next-build` | covered | More tools: Build and upload | `apple.next_build` | |
|
||||
| `apple encryption` | covered | App info tab: export compliance | `apple.encryption` | |
|
||||
| `apple ppo` | covered | Growth tab: PPO tests (start / stop = owner actions) | `apple.ppo_status`, `apple.ppo_create`, `apple.ppo_upload`, `owner.ppo_start`, `owner.ppo_stop` | |
|
||||
| `apple testflight` | covered | TestFlight tab (status, groups, What to Test, distribute, testers = owner action); info and wait in More tools | `apple.testflight_status`, `apple.testflight_groups`, `apple.testflight_what_to_test`, `apple.testflight_distribute`, `owner.testflight_testers`, `apple.testflight_info`, `apple.testflight_wait` | expire |
|
||||
| `icons` | covered | Store art tab: icons | `icons` | |
|
||||
| `shots capture` | covered | More tools: Captures | `shots.capture` | |
|
||||
| `shots login` | covered | More tools: owner steps (terminal command) | | signing in to a demo account by hand in a visible browser |
|
||||
| `shots fonts` | covered | More tools: Captures | `shots.fonts` | |
|
||||
| `shots render` | covered | Store art tab: Render | `shots.render` | |
|
||||
| `shots jpeg` | covered | More tools: Captures | `shots.jpeg` | |
|
||||
| `shots check` | covered | More tools: Captures | `shots.check` | |
|
||||
| `shots record` | covered | More tools: Captures | `shots.record` | |
|
||||
| `shots video` | covered | Store art tab: previews | `shots.video` | |
|
||||
| `shots gallery` | covered | Store art tab: gallery, review page | `shots.gallery`, `project.renders` | |
|
||||
44
tests/desktop-coverage.test.ts
Normal file
44
tests/desktop-coverage.test.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// Every CLI command is reachable in the desktop app, or listed as not for the desktop with a reason
|
||||
// (cli/lib/desktop-coverage.ts; the same analysis writes docs/desktop-coverage.md).
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { DESKTOP_COVERAGE, OWNER_STEPS, TOOLBOX } from '../cli/lib/desktop-coverage';
|
||||
import { describeMethods } from '../cli/serve';
|
||||
import { analyze, render } from '../tools/gen-desktop-coverage';
|
||||
|
||||
/** The only commands that make no sense in a window. Adding one here needs a reason in the coverage map. */
|
||||
const NOT_FOR_DESKTOP = ['mcp', 'serve', 'desktop', 'api'];
|
||||
|
||||
describe('desktop coverage', () => {
|
||||
const { rows, extra } = analyze();
|
||||
test('every CLI command has a desktop place (no gaps)', () => {
|
||||
expect(rows.filter(r => r.status === 'gap').map(r => `${r.command}: ${r.problems.join('; ')}`)).toEqual([]);
|
||||
expect(extra).toEqual([]);
|
||||
});
|
||||
test('not-for-desktop is short and each has a reason', () => {
|
||||
const not = rows.filter(r => r.status === 'not-for-desktop');
|
||||
expect(not.map(r => r.command).sort()).toEqual([...NOT_FOR_DESKTOP].sort());
|
||||
for (const r of not) expect(r.where.length).toBeGreaterThan(20);
|
||||
});
|
||||
test('the More tools tab offers no owner action and no unknown method', () => {
|
||||
const ms = new Map(describeMethods().map(m => [m.method, m] as const));
|
||||
for (const m of TOOLBOX.flatMap(g => g.methods)) {
|
||||
expect(ms.has(m)).toBe(true);
|
||||
expect(['read', 'write']).toContain(ms.get(m)!.kind);
|
||||
}
|
||||
for (const s of OWNER_STEPS) expect(s.command.startsWith('storebound ')).toBe(true);
|
||||
for (const [, c] of Object.entries(DESKTOP_COVERAGE)) if ('owner' in c && c.owner) expect(c.owner.length).toBeGreaterThan(2);
|
||||
});
|
||||
test('docs/desktop-coverage.md is generated and current', () => {
|
||||
expect(readFileSync(join(import.meta.dir, '../docs/desktop-coverage.md'), 'utf8')).toBe(render(rows));
|
||||
});
|
||||
test('every More tools group and owner step has en and tr strings', () => {
|
||||
const en = readFileSync(join(import.meta.dir, '../apps/desktop/src/i18n/en.ts'), 'utf8');
|
||||
const tr = readFileSync(join(import.meta.dir, '../apps/desktop/src/i18n/tr.ts'), 'utf8');
|
||||
for (const k of [...TOOLBOX.map(g => `tools.g.${g.id}`), ...OWNER_STEPS.map(s => `tools.o.${s.id}`)]) {
|
||||
expect(en).toContain(`'${k}'`);
|
||||
expect(tr).toContain(`'${k}'`);
|
||||
}
|
||||
});
|
||||
});
|
||||
86
tools/gen-desktop-coverage.ts
Normal file
86
tools/gen-desktop-coverage.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// docs/desktop-coverage.md from cli/lib/desktop-coverage.ts, checked against the code: a command is "covered" only
|
||||
// when every method it names exists (serve.ts describeMethods(), or a #[tauri::command] of src-tauri) and the window
|
||||
// calls it (the method quoted in apps/desktop/src, or listed in TOOLBOX, which the More tools tab renders).
|
||||
// `bun tools/gen-desktop-coverage.ts` writes the page; `--check` exits 1 when it is stale. tests/desktop-coverage.test.ts
|
||||
// runs analyze() too.
|
||||
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { COMMANDS, PLAIN } from '../cli/commands';
|
||||
import { DESKTOP_COVERAGE, OWNER_STEPS, TOOLBOX } from '../cli/lib/desktop-coverage';
|
||||
import { describeMethods } from '../cli/serve';
|
||||
|
||||
const ROOT = join(import.meta.dir, '..');
|
||||
const OUT = join(ROOT, 'docs/desktop-coverage.md');
|
||||
|
||||
function files(dir: string, out: string[] = []): string[] {
|
||||
for (const f of readdirSync(dir)) {
|
||||
const p = join(dir, f);
|
||||
if (statSync(p).isDirectory()) { if (f !== 'dev' && f !== 'i18n') files(p, out); } else if (/\.(ts|tsx|rs)$/.test(f)) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export interface Row { command: string; status: 'covered' | 'gap' | 'not-for-desktop'; where: string; methods: string[]; owner?: string; problems: string[] }
|
||||
|
||||
export function analyze(): { rows: Row[]; extra: string[] } {
|
||||
const src = files(join(ROOT, 'apps/desktop/src')).map(f => readFileSync(f, 'utf8')).join('\n');
|
||||
const rust = files(join(ROOT, 'apps/desktop/src-tauri/src')).map(f => readFileSync(f, 'utf8')).join('\n');
|
||||
const hostCmds = new Set([...rust.matchAll(/#\[tauri::command\][\s\S]{0,200}?fn ([a-z_]+)/g)].map(m => m[1]!));
|
||||
const methods = new Set(describeMethods().map(m => m.method));
|
||||
const toolboxShown = /\bTOOLBOX\b/.test(src);
|
||||
const inToolbox = new Set(TOOLBOX.flatMap(g => g.methods));
|
||||
const reachable = (m: string): string | null => {
|
||||
if (m.startsWith('host:')) {
|
||||
const c = m.slice(5);
|
||||
if (!hostCmds.has(c)) return `${m}: no such tauri command`;
|
||||
return src.includes(`'${c}'`) ? null : `${m}: never invoked by the window`;
|
||||
}
|
||||
if (!methods.has(m)) return `${m}: no such serve method`;
|
||||
if (src.includes(`'${m}'`) || src.includes(`"${m}"`)) return null;
|
||||
if (toolboxShown && inToolbox.has(m)) return null;
|
||||
return `${m}: no window place calls it`;
|
||||
};
|
||||
const all = [...Object.keys(PLAIN), ...Object.keys(COMMANDS)];
|
||||
const rows: Row[] = all.map(command => {
|
||||
const c = DESKTOP_COVERAGE[command];
|
||||
if (!c) return { command, status: 'gap', where: '', methods: [], problems: ['no entry in cli/lib/desktop-coverage.ts'] };
|
||||
if ('not' in c) return { command, status: 'not-for-desktop', where: c.not, methods: [], problems: c.not.trim() ? [] : ['not-for-desktop without a reason'] };
|
||||
const problems = c.methods.map(reachable).filter((p): p is string => !!p);
|
||||
if (!c.methods.length && !(c.owner && toolboxShown && OWNER_STEPS.length)) problems.push('no method and no owner step');
|
||||
return { command, status: problems.length ? 'gap' : 'covered', where: c.where, methods: c.methods, ...(c.owner ? { owner: c.owner } : {}), problems };
|
||||
});
|
||||
return { rows, extra: Object.keys(DESKTOP_COVERAGE).filter(k => !all.includes(k)) };
|
||||
}
|
||||
|
||||
export function render(rows: Row[]): string {
|
||||
const n = (s: Row['status']): number => rows.filter(r => r.status === s).length;
|
||||
const cell = (s: string): string => s.replace(/\|/g, '\\|');
|
||||
return [
|
||||
'# Desktop coverage',
|
||||
'',
|
||||
'Generated by `bun tools/gen-desktop-coverage.ts` from `cli/lib/desktop-coverage.ts`; do not edit by hand.',
|
||||
'Every CLI command (cli/commands.ts) and the desktop place that runs it. "covered" means each method listed exists',
|
||||
'(serve.ts, or a host command `host:`) and the window calls it; tests/desktop-coverage.test.ts fails on a gap.',
|
||||
'Owner-only parts are owner actions in the window or owner steps with their terminal command (More tools).',
|
||||
'',
|
||||
`**${rows.length} commands:** ${n('covered')} covered, ${n('gap')} gaps, ${n('not-for-desktop')} not for the desktop.`,
|
||||
'',
|
||||
'| command | status | where in the window | methods | owner step |',
|
||||
'|---|---|---|---|---|',
|
||||
...rows.map(r => `| \`${r.command}\` | ${r.status} | ${cell(r.where || r.problems.join('; '))} | ${r.methods.map(m => `\`${m}\``).join(', ')} | ${cell(r.owner ?? '')} |`),
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
const { rows } = analyze();
|
||||
const md = render(rows);
|
||||
if (process.argv.includes('--check')) {
|
||||
let cur = '';
|
||||
try { cur = readFileSync(OUT, 'utf8'); } catch { /* missing */ }
|
||||
if (cur !== md) { console.error('docs/desktop-coverage.md is stale: run bun tools/gen-desktop-coverage.ts'); process.exit(1); }
|
||||
} else writeFileSync(OUT, md);
|
||||
const gaps = rows.filter(r => r.status === 'gap');
|
||||
for (const g of gaps) console.log(`gap: ${g.command}: ${g.problems.join('; ')}`);
|
||||
console.log(`${rows.length} commands, ${rows.filter(r => r.status === 'covered').length} covered, ${gaps.length} gaps`);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue