UNPKG

calibre

Version:

Performance monitoring with Synthetic testing, Chrome UX Report, and Real User Metrics

1,075 lines (809 loc) 31.7 kB
# Calibre CLI v7.0.0 Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task. **Goal:** Restructure the CLI into Synthetic/CrUX/RUM namespaces, add 8 new field data commands, maintain backwards compatibility via hidden deprecated aliases. **Architecture:** Move existing command files into new directories (`synthetic/`, `deploy/`), create new API modules and CLI commands for CrUX and RUM, wire hidden deprecated aliases in the slimmed-down `site.js`, and update all docs/examples. All existing GraphQL API patterns are followed identically. **Tech Stack:** JavaScript (ES Modules), yargs 18, graphql-request 7, chalk, columnify, ora, Jest **Design doc:** `docs/plans/2026-05-05-v7-restructure-design.md` **Changelog spec:** `CHANGELOG-v7-DRAFT.md` --- ### Task 1: Create deprecation utility **Files:** - Create: `src/utils/deprecation.js` - Test: `__tests__/utils/deprecation.test.js` **Step 1: Write the test** ```javascript import { deprecatedHandler } from '../../src/utils/deprecation.js' describe('deprecatedHandler', () => { let stderrOutput const originalWrite = process.stderr.write beforeEach(() => { stderrOutput = '' process.stderr.write = (chunk) => { stderrOutput += chunk } delete process.env.CALIBRE_SUPPRESS_DEPRECATIONS }) afterEach(() => { process.stderr.write = originalWrite delete process.env.CALIBRE_SUPPRESS_DEPRECATIONS }) test('prints deprecation warning to stderr', async () => { const inner = jest.fn() const handler = deprecatedHandler('site pages', 'synthetic pages', inner) await handler({ site: 'test' }) expect(stderrOutput).toContain('[calibre:deprecated]') expect(stderrOutput).toContain('site pages') expect(stderrOutput).toContain('synthetic pages') expect(inner).toHaveBeenCalledWith({ site: 'test' }) }) test('suppresses warning when CALIBRE_SUPPRESS_DEPRECATIONS is set', async () => { process.env.CALIBRE_SUPPRESS_DEPRECATIONS = '1' const inner = jest.fn() const handler = deprecatedHandler('site pages', 'synthetic pages', inner) await handler({ site: 'test' }) expect(stderrOutput).toBe('') expect(inner).toHaveBeenCalled() }) }) ``` **Step 2: Run test to verify it fails** Run: `npm test -- __tests__/utils/deprecation.test.js` Expected: FAIL — module not found **Step 3: Write implementation** ```javascript import chalk from 'chalk' const deprecatedHandler = (oldCommand, newCommand, handler) => { return async (args) => { if (!process.env.CALIBRE_SUPPRESS_DEPRECATIONS) { process.stderr.write( chalk.yellow( `[calibre:deprecated] "${oldCommand}" has moved to "${newCommand}"\n` ) ) } return handler(args) } } export { deprecatedHandler } ``` **Step 4: Run test to verify it passes** Run: `npm test -- __tests__/utils/deprecation.test.js` Expected: PASS **Step 5: Lint** Run: `npm run lint` Expected: No errors **Step 6: Commit** ```bash git add src/utils/deprecation.js __tests__/utils/deprecation.test.js git commit -m "feat: add deprecation handler utility for v7 command restructure" ``` --- ### Task 2: Create shared option modules **Files:** - Create: `src/utils/crux-options.js` - Create: `src/utils/rum-options.js` **Step 1: Create CrUX options** ```javascript const cruxOptions = { formFactor: { describe: 'Filter by device type.', choices: ['desktop', 'phone', 'tablet'], type: 'string' }, timePeriod: { describe: 'History time window.', choices: [ 'three-months', 'six-months', 'nine-months', 'twelve-months', 'eighteen-months', 'twenty-four-months' ], default: 'six-months', type: 'string' } } export { cruxOptions } ``` **Step 2: Create RUM options** ```javascript import { options } from './cli.js' const rumFilterOptions = { duration: { describe: 'Number of days to aggregate.', default: 7, type: 'number' }, dateBin: { describe: 'Time granularity.', choices: ['day', 'month'], default: 'day', type: 'string' }, country: { describe: 'Filter by country code(s) (space-separated, e.g. AU US).', type: 'array' }, device: { describe: 'Filter by device type.', choices: ['desktop', 'mobile', 'tablet'], type: 'string' }, connection: { describe: 'Filter by connection type.', type: 'array' }, path: { describe: 'Filter by URL path(s) (space-separated).', type: 'array' }, pageGrouping: { describe: 'Filter by page grouping UUID(s) (space-separated).', type: 'array' } } export { rumFilterOptions } ``` **Step 3: Lint** Run: `npm run lint` Expected: No errors **Step 4: Commit** ```bash git add src/utils/crux-options.js src/utils/rum-options.js git commit -m "feat: add shared CrUX and RUM option definitions" ``` --- ### Task 3: Create grading view utility **Files:** - Create: `src/views/grading.js` - Test: `__tests__/views/grading.test.js` **Step 1: Write the test** ```javascript import { formatGrading } from '../../src/views/grading.js' describe('formatGrading', () => { test('returns Good for good grading', () => { const result = formatGrading('good') expect(result).toContain('Good') }) test('returns NI for needs-improvement grading', () => { const result = formatGrading('needs-improvement') expect(result).toContain('NI') }) test('returns Poor for poor grading', () => { const result = formatGrading('poor') expect(result).toContain('Poor') }) test('returns — for null', () => { const result = formatGrading(null) expect(result).toBe('—') }) }) ``` **Step 2: Run test to verify it fails** Run: `npm test -- __tests__/views/grading.test.js` Expected: FAIL **Step 3: Write implementation** ```javascript import chalk from 'chalk' const formatGrading = (grading) => { if (!grading) return '—' switch (grading) { case 'good': return chalk.green('Good') case 'needs-improvement': return chalk.yellow('NI') case 'poor': return chalk.red('Poor') default: return grading } } export { formatGrading } ``` **Step 4: Run test to verify it passes** Run: `npm test -- __tests__/views/grading.test.js` Expected: PASS **Step 5: Commit** ```bash git add src/views/grading.js __tests__/views/grading.test.js git commit -m "feat: add grading formatter with accessible text+colour output" ``` --- ### Task 4: Move synthetic commands and create router **Files:** - Move: 17 files from `src/cli/site/` to `src/cli/synthetic/` (see list below) - Modify: `src/cli/synthetic/download-artifacts.js` (rename command string) - Modify: `src/cli/synthetic/create-pull-request-review.js` (update hint message) - Create: `src/cli/synthetic.js` **Step 1: Create synthetic directory and move files** ```bash mkdir -p src/cli/synthetic git mv src/cli/site/pages.js src/cli/synthetic/pages.js git mv src/cli/site/create-page.js src/cli/synthetic/create-page.js git mv src/cli/site/update-page.js src/cli/synthetic/update-page.js git mv src/cli/site/delete-page.js src/cli/synthetic/delete-page.js git mv src/cli/site/snapshots.js src/cli/synthetic/snapshots.js git mv src/cli/site/create-snapshot.js src/cli/synthetic/create-snapshot.js git mv src/cli/site/delete-snapshot.js src/cli/synthetic/delete-snapshot.js git mv src/cli/site/download-snapshot-artifacts.js src/cli/synthetic/download-artifacts.js git mv src/cli/site/get-snapshot-metrics.js src/cli/synthetic/get-snapshot-metrics.js git mv src/cli/site/metrics.js src/cli/synthetic/metrics.js git mv src/cli/site/test-profiles.js src/cli/synthetic/test-profiles.js git mv src/cli/site/create-test-profile.js src/cli/synthetic/create-test-profile.js git mv src/cli/site/update-test-profile.js src/cli/synthetic/update-test-profile.js git mv src/cli/site/delete-test-profile.js src/cli/synthetic/delete-test-profile.js git mv src/cli/site/pull-request-reviews.js src/cli/synthetic/pull-request-reviews.js git mv src/cli/site/create-pull-request-review.js src/cli/synthetic/create-pull-request-review.js git mv src/cli/site/pull-request-review.js src/cli/synthetic/pull-request-review.js ``` **Step 2: Update command string in download-artifacts.js** In `src/cli/synthetic/download-artifacts.js`, change: ```javascript const command = 'download-snapshot-artifacts [options]' ``` to: ```javascript const command = 'download-artifacts [options]' ``` **Step 3: Update hint in create-pull-request-review.js** In `src/cli/synthetic/create-pull-request-review.js`, change: ```javascript `View progress by running \`calibre site pull-request-review ${args.branch} --site=${args.site}\`` ``` to: ```javascript `View progress by running \`calibre synthetic pull-request-review ${args.branch} --site=${args.site}\`` ``` **Step 4: Create the synthetic group router** Write `src/cli/synthetic.js`: ```javascript import * as Pages from './synthetic/pages.js' import * as CreatePage from './synthetic/create-page.js' import * as UpdatePage from './synthetic/update-page.js' import * as DeletePage from './synthetic/delete-page.js' import * as Snapshots from './synthetic/snapshots.js' import * as CreateSnapshot from './synthetic/create-snapshot.js' import * as DeleteSnapshot from './synthetic/delete-snapshot.js' import * as DownloadArtifacts from './synthetic/download-artifacts.js' import * as GetSnapshotMetrics from './synthetic/get-snapshot-metrics.js' import * as Metrics from './synthetic/metrics.js' import * as TestProfiles from './synthetic/test-profiles.js' import * as CreateTestProfile from './synthetic/create-test-profile.js' import * as UpdateTestProfile from './synthetic/update-test-profile.js' import * as DeleteTestProfile from './synthetic/delete-test-profile.js' import * as PullRequestReviews from './synthetic/pull-request-reviews.js' import * as CreatePullRequestReview from './synthetic/create-pull-request-review.js' import * as PullRequestReview from './synthetic/pull-request-review.js' const commands = [ Pages, CreatePage, UpdatePage, DeletePage, Snapshots, CreateSnapshot, DeleteSnapshot, DownloadArtifacts, GetSnapshotMetrics, Metrics, TestProfiles, CreateTestProfile, UpdateTestProfile, DeleteTestProfile, PullRequestReviews, CreatePullRequestReview, PullRequestReview ] const command = 'synthetic <command>' const desc = 'Synthetic monitoring — manage scheduled Lighthouse tests, Pages, Test Profiles, Snapshots, and Pull Request Reviews.' const builder = yargs => { return yargs.commands(commands) } const handler = () => {} export { command, desc, builder, handler, commands } ``` **Step 5: Lint** Run: `npm run lint` Expected: No errors **Step 6: Commit** ```bash git add -A git commit -m "refactor: move synthetic commands from site/ to synthetic/" ``` --- ### Task 5: Move deploy commands and create router **Files:** - Move: 3 files from `src/cli/site/` to `src/cli/deploy/` - Modify: command strings and pagination hint - Create: `src/cli/deploy.js` **Step 1: Create deploy directory and move files** ```bash mkdir -p src/cli/deploy git mv src/cli/site/deploys.js src/cli/deploy/list.js git mv src/cli/site/create-deploy.js src/cli/deploy/create.js git mv src/cli/site/delete-deploy.js src/cli/deploy/delete.js ``` **Step 2: Update command strings** In `src/cli/deploy/list.js`, change: ```javascript const command = 'deploys [options]' const describe = 'List all deployments for a Site.' ``` to: ```javascript const command = 'list [options]' const describe = 'List all deployments for a Site.' ``` Also update the pagination hint from: ```javascript `To see deploys after ${ lastDeploy.revision || lastDeploy.id }, run: calibre site deploys --site=calibre --cursor=${ index.pageInfo.endCursor }` ``` to: ```javascript `To see deploys after ${ lastDeploy.revision || lastDeploy.id }, run: calibre deploy list --site=${args.site} --cursor=${ index.pageInfo.endCursor }` ``` In `src/cli/deploy/create.js`, change: ```javascript const command = 'create-deploy [options]' const describe = 'Create a deployment.' ``` to: ```javascript const command = 'create [options]' const describe = 'Create a deployment.' ``` In `src/cli/deploy/delete.js`, change: ```javascript const command = 'delete-deploy [options]' const describe = 'Delete a deploy from a selected Site.' ``` to: ```javascript const command = 'delete [options]' const describe = 'Delete a deploy from a selected Site.' ``` **Step 3: Create the deploy group router** Write `src/cli/deploy.js`: ```javascript import * as DeployList from './deploy/list.js' import * as DeployCreate from './deploy/create.js' import * as DeployDelete from './deploy/delete.js' const commands = [DeployList, DeployCreate, DeployDelete] const command = 'deploy <command>' const desc = 'Manage deployment markers — annotate your performance charts across Synthetic, CrUX, and RUM data.' const builder = yargs => { return yargs.commands(commands) } const handler = () => {} export { command, desc, builder, handler, commands } ``` **Step 4: Lint** Run: `npm run lint` Expected: No errors **Step 5: Commit** ```bash git add -A git commit -m "refactor: move deploy commands from site/ to deploy/" ``` --- ### Task 6: Wire deprecations in site.js and update cli-commands.js **Files:** - Modify: `src/cli/site.js` - Modify: `src/cli-commands.js` **Step 1: Rewrite site.js** Replace all contents of `src/cli/site.js` with the slimmed version containing hidden deprecated wrappers. The file imports from the new locations (`./synthetic/`, `./deploy/`), exports only `create`, `list`, `delete` as visible commands, and registers all 20 deprecated commands with `describe: false` and `deprecatedHandler`-wrapped handlers. See the design doc Phase 4 for the full structure. Every deprecated command must: - Use the **old** command string (e.g., `'pages [options]'`, `'deploys [options]'`, `'create-deploy [options]'`) - Set `describe: false` - Use `builder` from the original module - Wrap `handler` with `deprecatedHandler(oldPath, newPath, originalHandler)` Full list of 20 deprecated entries: | Old command string | Old path | New path | |---|---|---| | `pages [options]` | `site pages` | `synthetic pages` | | `create-page <name> [options]` | `site create-page` | `synthetic create-page` | | `update-page [options]` | `site update-page` | `synthetic update-page` | | `delete-page [options]` | `site delete-page` | `synthetic delete-page` | | `snapshots [options]` | `site snapshots` | `synthetic snapshots` | | `create-snapshot [options]` | `site create-snapshot` | `synthetic create-snapshot` | | `delete-snapshot [options]` | `site delete-snapshot` | `synthetic delete-snapshot` | | `download-snapshot-artifacts [options]` | `site download-snapshot-artifacts` | `synthetic download-artifacts` | | `get-snapshot-metrics [options]` | `site get-snapshot-metrics` | `synthetic get-snapshot-metrics` | | `metrics [options]` | `site metrics` | `synthetic metrics` | | `test-profiles [options]` | `site test-profiles` | `synthetic test-profiles` | | `create-test-profile <name> [options]` | `site create-test-profile` | `synthetic create-test-profile` | | `update-test-profile [options]` | `site update-test-profile` | `synthetic update-test-profile` | | `delete-test-profile [options]` | `site delete-test-profile` | `synthetic delete-test-profile` | | `pull-request-reviews [options]` | `site pull-request-reviews` | `synthetic pull-request-reviews` | | `create-pull-request-review [options]` | `site create-pull-request-review` | `synthetic create-pull-request-review` | | `pull-request-review <branch>` | `site pull-request-review` | `synthetic pull-request-review` | | `deploys [options]` | `site deploys` | `deploy list` | | `create-deploy [options]` | `site create-deploy` | `deploy create` | | `delete-deploy [options]` | `site delete-deploy` | `deploy delete` | **Step 2: Update cli-commands.js** Add imports for `Synthetic`, `Deploy` (and stub imports for `Crux`, `Rum` — these will be created in Tasks 9-10 but we need the import to exist). For now, only add `Synthetic` and `Deploy` — `Crux` and `Rum` will be added in their respective tasks. ```javascript import * as ConnectionList from './cli/connection-list.js' import * as DeviceList from './cli/device-list.js' import * as LocationList from './cli/location-list.js' import * as MetricList from './cli/metric-list.js' import * as Request from './cli/request.js' import * as Site from './cli/site.js' import * as Synthetic from './cli/synthetic.js' import * as Deploy from './cli/deploy.js' import * as Team from './cli/team.js' import * as Test from './cli/test.js' import * as Token from './cli/token.js' const commands = [ Site, Synthetic, Deploy, Test, Team, ConnectionList, DeviceList, LocationList, MetricList, Token, Request ] export default commands ``` **Step 3: Lint and test** Run: `npm run lint && npm test` Expected: lint passes, existing tests may need snapshot updates (see Task 7) **Step 4: Commit** ```bash git add src/cli/site.js src/cli-commands.js git commit -m "feat: wire deprecated aliases in site.js, add synthetic and deploy to cli-commands" ``` --- ### Task 7: Update existing tests for moved commands **Files:** - Move: `__tests__/cli/site/snapshots.test.js` → `__tests__/cli/synthetic/snapshots.test.js` - Move: `__tests__/cli/site/pages.test.js` → `__tests__/cli/synthetic/pages.test.js` - Move: `__tests__/cli/site/get-snapshot-metrics.test.js` → `__tests__/cli/synthetic/get-snapshot-metrics.test.js` - Create: `__tests__/cli/deprecation.test.js` **Step 1: Move test files and update command paths** ```bash mkdir -p __tests__/cli/synthetic git mv __tests__/cli/site/snapshots.test.js __tests__/cli/synthetic/snapshots.test.js git mv __tests__/cli/site/pages.test.js __tests__/cli/synthetic/pages.test.js git mv __tests__/cli/site/get-snapshot-metrics.test.js __tests__/cli/synthetic/get-snapshot-metrics.test.js ``` In each moved test file, update the CLI args from `'site <subcommand>'` to `'synthetic <subcommand>'`. For example in `snapshots.test.js`: - Change `args: 'site snapshots --site=test'` to `args: 'synthetic snapshots --site=test'` In `pages.test.js`: - Change `args: 'site pages --site=test'` to `args: 'synthetic pages --site=test'` In `get-snapshot-metrics.test.js`: - Change `args: 'site get-snapshot-metrics --site=test --snapshot=1000'` to `args: 'synthetic get-snapshot-metrics --site=test --snapshot=1000'` **Step 2: Delete old snapshots and regenerate** ```bash rm -rf __tests__/cli/synthetic/__snapshots__ npm test -- --updateSnapshot __tests__/cli/synthetic/ ``` **Step 3: Write deprecation integration test** Create `__tests__/cli/deprecation.test.js`: ```javascript import { runCLI, setupIntegrationServer, teardownIntegrationServer } from '../utils' import listPages from '../fixtures/listPages.json' describe('deprecated commands', () => { beforeAll(async () => await setupIntegrationServer(listPages)) afterAll(async () => await teardownIntegrationServer()) test('site pages shows deprecation warning on stderr', async () => { const stderr = await runCLI({ args: 'site pages --site=test', testForError: true }) expect(stderr).toContain('[calibre:deprecated]') expect(stderr).toContain('synthetic pages') }) test('site pages still returns valid output on stdout', async () => { const stdout = await runCLI({ args: 'site pages --site=test' }) expect(stdout).toBeTruthy() }) }) ``` **Step 4: Run all tests** Run: `npm test` Expected: PASS **Step 5: Commit** ```bash git add -A git commit -m "test: update tests for synthetic/deploy restructure, add deprecation tests" ``` --- ### Task 8: Enhance site list with monitoring status **Files:** - Modify: `src/api/site.js` (LIST_QUERY) - Modify: `src/cli/site/list.js` (table output) **Step 1: Update LIST_QUERY in `src/api/site.js`** Add `monitoringStatus` to the query: ```graphql query { organisation { sites { name slug createdAt team { name slug } monitoringStatus { synthetic crux rum } } } } ``` **Step 2: Update `src/cli/site/list.js` table** Add a `monitoring` column that shows active statuses. In the `rows` map: ```javascript const rows = index.map(row => { const statuses = [] if (row.monitoringStatus?.synthetic) statuses.push(chalk.green('synthetic')) if (row.monitoringStatus?.crux) statuses.push(chalk.green('crux')) if (row.monitoringStatus?.rum) statuses.push(chalk.green('rum')) return { slug: chalk.grey(row.slug), name: row.name, monitoring: statuses.join(' ') || chalk.grey('—'), created: `${dateFormat(new Date(row.createdAt), 'h:mma d-MMM-yyyy')}` } }) ``` JSON output passes through the raw API response unchanged (it already includes `monitoringStatus`). **Step 3: Lint and test** Run: `npm run lint && npm test` Expected: PASS **Step 4: Commit** ```bash git add src/api/site.js src/cli/site/list.js git commit -m "feat: show monitoring status (synthetic/crux/rum) in site list" ``` --- ### Task 9: Implement CrUX API module and commands **Files:** - Create: `src/api/crux.js` - Create: `src/cli/crux.js` - Create: `src/cli/crux/summary.js` - Create: `src/cli/crux/history.js` - Create: `src/cli/crux/urls.js` - Create: `src/cli/crux/url.js` - Create: `__tests__/fixtures/cruxSummary.json` - Create: `__tests__/cli/crux/summary.test.js` - Modify: `src/cli-commands.js` (add Crux) This is a large task. See the design doc Phases 8 for full GraphQL queries (derived from the queries provided in the user's initial message). Each command follows the exact same pattern as existing commands: spinner → API call → format output (JSON/CSV/table). **Step 1: Create API module `src/api/crux.js`** Four functions: `summary`, `history`, `urls`, `url`. Each builds a GraphQL query string and calls `request()`. Query shapes are derived from `GetCruxData`, `ListCruxUrls`, and `GetCruxUrlData` as provided by the user. Map `--form-factor` CLI values to GraphQL enum: `desktop` → `DESKTOP`, `phone` → `PHONE`, `tablet` → `TABLET`. Map `--time-period` CLI values to GraphQL enum: `three-months` → `THREE_MONTHS`, etc. **Step 2: Create CLI commands** Each command file in `src/cli/crux/` follows the standard pattern: - Import from `../../api/crux.js` - Import `options` from `../../utils/cli.js` and `cruxOptions` from `../../utils/crux-options.js` - Export `command`, `describe`, `builder`, `handler` - Handler: spinner → API call → JSON/CSV/table output Key details per command: - `summary.js`: describe = `'Display Chrome UX Report (CrUX) origin-level performance data and Core Web Vitals assessment.'` - `history.js`: describe = `'Display Chrome UX Report (CrUX) historical trends for a site.'`; add `--limit` option (default 25) - `urls.js`: describe = `'List Chrome UX Report (CrUX) monitored URLs with their metrics and Core Web Vitals assessment.'` - `url.js`: command = `'url <uuid> [options]'`; describe = `'Display Chrome UX Report (CrUX) data for a specific monitored URL.'` When API returns null/empty data, print: `No CrUX data available for this site. CrUX requires sufficient Chrome user traffic.` **Step 3: Create group router `src/cli/crux.js`** ```javascript import * as Summary from './crux/summary.js' import * as History from './crux/history.js' import * as Urls from './crux/urls.js' import * as Url from './crux/url.js' const commands = [Summary, History, Urls, Url] const command = 'crux <command>' const desc = 'Chrome UX Report (CrUX) — real-world performance data from Chrome users.' const builder = yargs => { return yargs.commands(commands) } const handler = () => {} export { command, desc, builder, handler, commands } ``` **Step 4: Add Crux to cli-commands.js** Add `import * as Crux from './cli/crux.js'` and add `Crux` to the commands array (after `Deploy`). **Step 5: Create test fixtures and tests** Create `__tests__/fixtures/cruxSummary.json` with a mock response matching the `GetCruxData` query shape. Write `__tests__/cli/crux/summary.test.js` following the existing test pattern (mock server, `runCLI`, snapshot). **Step 6: Lint and test** Run: `npm run lint && npm test` Expected: PASS **Step 7: Commit** ```bash git add -A git commit -m "feat: add CrUX commands (summary, history, urls, url)" ``` --- ### Task 10: Implement RUM API module and commands **Files:** - Create: `src/api/rum.js` - Create: `src/cli/rum.js` - Create: `src/cli/rum/summary.js` - Create: `src/cli/rum/history.js` - Create: `src/cli/rum/pages.js` - Create: `src/cli/rum/config.js` - Create: `__tests__/fixtures/rumSummary.json` - Create: `__tests__/cli/rum/summary.test.js` - Modify: `src/cli-commands.js` (add Rum) Follows the same pattern as Task 9. **Step 1: Create API module `src/api/rum.js`** Four functions: `summary`, `history`, `pages`, `config`. Query shapes from `GetSiteRumDashboard`, `GetSiteRumDashboardHistory`, `GetSiteRumPages`, and site rumConfig field. Build the `RumFilterInput` from CLI flags: - `--duration` → `filter.duration` (number) - `--date-bin` → `filter.dateBin` (`day` → `DAY`, `month` → `MONTH`) - `--country` → `filter.countryCode` (array) - `--device` → `filter.isDesktopDevice`/`isMobileDevice`/`isTabletDevice` (boolean flags) - `--path` → `filter.path` (array) - `--page-grouping` → passed as `pageGroupingUuids` variable Metrics default: `['lcp', 'cls', 'inp', 'ttfb', 'fcp', 'rtt']` **Step 2: Create CLI commands** Each command in `src/cli/rum/`: - `summary.js`: Display live visitors, countries, aggregate metrics, UX ratings. Table format: `Metric | p75 | Rating | Good% | NI% | Poor%` - `history.js`: Display per-date metrics. Add `--limit` (default 25). Table format: `date | lcp | cls | inp | ttfb | fcp | sessions` - `pages.js`: Display page-level breakdown. Options: `--sort-by` (default `sessionCount`), `--limit` (default 25), `--offset` (default 0). Pagination hint on truncated output. - `config.js`: Display RUM configuration. Simple key-value output. When API returns null/empty: print `No RUM data available. Check that RUM is enabled for this site with: calibre rum config --site=<slug>` **Step 3: Create group router `src/cli/rum.js`** ```javascript import * as Summary from './rum/summary.js' import * as History from './rum/history.js' import * as Pages from './rum/pages.js' import * as Config from './rum/config.js' const commands = [Summary, History, Pages, Config] const command = 'rum <command>' const desc = 'Real User Metrics (RUM) — field performance data from your real users.' const builder = yargs => { return yargs.commands(commands) } const handler = () => {} export { command, desc, builder, handler, commands } ``` **Step 4: Add Rum to cli-commands.js** Add `import * as Rum from './cli/rum.js'` and add `Rum` to the commands array (after `Crux`). **Step 5: Create test fixtures and tests** **Step 6: Lint and test** Run: `npm run lint && npm test` Expected: PASS **Step 7: Commit** ```bash git add -A git commit -m "feat: add RUM commands (summary, history, pages, config)" ``` --- ### Task 11: Enhance metric-list with --type flag **Files:** - Modify: `src/api/metric.js` - Modify: `src/cli/metric-list.js` **Step 1: Add CrUX and RUM query support to `src/api/metric.js`** Add new queries for `cruxMetrics` and `rumMetrics` root fields. Add a `type` parameter to the `list` function that selects which query to run. **Step 2: Add --type flag to metric-list.js builder** ```javascript type: { describe: 'Filter metrics by data source.', choices: ['synthetic', 'crux', 'rum'] } ``` Pass `args.type` to the API `list()` call. **Step 3: Lint and test** Run: `npm run lint && npm test` Expected: PASS **Step 4: Commit** ```bash git add src/api/metric.js src/cli/metric-list.js git commit -m "feat: add --type flag to metric-list for filtering by data source" ``` --- ### Task 12: Update Node.js API exports **Files:** - Modify: `index.js` **Step 1: Add new exports** ```javascript export * as Crux from './src/api/crux.js' export * as Rum from './src/api/rum.js' ``` **Step 2: Build** Run: `npm run build` Expected: esbuild bundles `dist/index.cjs` without errors **Step 3: Commit** ```bash git add index.js git commit -m "feat: export Crux and Rum from Node.js API" ``` --- ### Task 13: Update documentation **Files:** - Modify: `README.md` - Modify: `package.json` (version, description, keywords) - Modify: `CHANGELOG.md` - Create: `examples/bash/crux-summary.sh` - Create: `examples/bash/rum-pages.sh` - Create: `examples/bash/deploy-create.sh` - Modify: `examples/bash/README.md` - Create: `examples/nodejs/crux/summary.js` - Create: `examples/nodejs/crux/history.js` - Create: `examples/nodejs/crux/urls.js` - Create: `examples/nodejs/rum/summary.js` - Create: `examples/nodejs/rum/pages.js` - Create: `examples/nodejs/rum/config.js` - Modify: `examples/nodejs/README.md` - Delete: `CHANGELOG-v7-DRAFT.md` **Step 1: Update package.json** - `"version": "7.0.0"` - `"description": "Performance monitoring with Synthetic testing, Chrome UX Report, and Real User Metrics"` - Add keywords: `"rum"`, `"crux"`, `"web-vitals"`, `"real-user-monitoring"` **Step 2: Update README.md** - Update features to mention three pillars - Update usage examples with new namespaces - Update package exports to show `Crux` and `Rum` **Step 3: Update CHANGELOG.md** Prepend content from `CHANGELOG-v7-DRAFT.md` to `CHANGELOG.md`. Delete `CHANGELOG-v7-DRAFT.md`. **Step 4: Create bash examples** Each example script follows the pattern in `examples/bash/create-test.sh`: set `-euo pipefail`, require `CALIBRE_API_TOKEN`, call CLI with `--json`, pipe through `jq`. **Step 5: Create Node.js examples** Each example follows the pattern in existing `examples/nodejs/` files: import from `'calibre'`, call API function, log result. **Step 6: Update example READMEs** Add new examples to the listings in both `examples/bash/README.md` and `examples/nodejs/README.md`. **Step 7: Regenerate CLI_COMMANDS.md** Run: `npm run generate-cli-md` Verify: - No deprecated `site <command>` entries - All new commands present - `site` section shows only `create`, `list`, `delete` **Step 8: Commit** ```bash git add -A git commit -m "docs: update README, CHANGELOG, examples, and CLI_COMMANDS.md for v7.0.0" ``` --- ### Task 14: Final verification **Step 1: Run full lint** Run: `npm run lint` Expected: Zero warnings, zero errors **Step 2: Run full test suite** Run: `npm test` Expected: All tests pass **Step 3: Run build** Run: `npm run build` Expected: `dist/index.cjs` built successfully **Step 4: Regenerate and diff CLI_COMMANDS.md** Run: `npm run generate-cli-md` Run: `git diff CLI_COMMANDS.md` Verify no deprecated commands appear, all new commands are present. **Step 5: Smoke test help output** Run: `node src/cli.js --help` Verify: `synthetic`, `deploy`, `crux`, `rum` appear. No deprecated entries. Run: `node src/cli.js site --help` Verify: Only `create`, `list`, `delete` shown. Run: `node src/cli.js synthetic --help` Verify: All 17 subcommands listed. **Step 6: Smoke test deprecation** Run: `node src/cli.js site pages --site=test 2>&1 | head -1` Verify: `[calibre:deprecated] "site pages" has moved to "synthetic pages"` **Step 7: Commit any final adjustments** ```bash git add -A git commit -m "chore: final verification and cleanup for v7.0.0" ```